diff --git a/library/vendor/dompdf/LICENSE.LGPL b/library/vendor/dompdf/LICENSE.LGPL new file mode 100644 index 000000000..6ef5de82a --- /dev/null +++ b/library/vendor/dompdf/LICENSE.LGPL @@ -0,0 +1,456 @@ + 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. \ No newline at end of file diff --git a/library/vendor/dompdf/SOURCE b/library/vendor/dompdf/SOURCE new file mode 100644 index 000000000..9343b58cf --- /dev/null +++ b/library/vendor/dompdf/SOURCE @@ -0,0 +1,8 @@ +curl https://codeload.github.com/dompdf/dompdf/tar.gz/v0.6.1 -o dompdf-0.6.1.tar.gz +tar xzf dompdf-0.6.1.tar.gz --strip-components 1 dompdf-0.6.1/{include/*.php,lib,dompdf*.php,LICENSE.LGPL} +rm dompdf-0.6.1.tar.gz + +curl https://codeload.github.com/PhenX/php-font-lib/tar.gz/0.3.1 -o php-font-lib-0.3.1.tar.gz +mkdir lib/php-font-lib/classes +tar xzf php-font-lib-0.3.1.tar.gz --strip-components 3 -C lib/php-font-lib/classes php-font-lib-0.3.1/src/FontLib +rm php-font-lib-0.3.1.tar.gz diff --git a/library/vendor/dompdf/dompdf.php b/library/vendor/dompdf/dompdf.php new file mode 100755 index 000000000..a9052fa46 --- /dev/null +++ b/library/vendor/dompdf/dompdf.php @@ -0,0 +1,289 @@ + + * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + */ + +/** + * Display command line usage + */ +function dompdf_usage() { + $default_paper_size = DOMPDF_DEFAULT_PAPER_SIZE; + + echo <<load_html($str); + +} else + $dompdf->load_html_file($file); + +if ( isset($base_path) ) { + $dompdf->set_base_path($base_path); +} + +$dompdf->set_paper($paper, $orientation); + +$dompdf->render(); + +if ( $_dompdf_show_warnings ) { + global $_dompdf_warnings; + foreach ($_dompdf_warnings as $msg) + echo $msg . "\n"; + echo $dompdf->get_canvas()->get_cpdf()->messages; + flush(); +} + +if ( $save_file ) { +// if ( !is_writable($outfile) ) +// throw new DOMPDF_Exception("'$outfile' is not writable."); + if ( strtolower(DOMPDF_PDF_BACKEND) === "gd" ) + $outfile = str_replace(".pdf", ".png", $outfile); + + list($proto, $host, $path, $file) = explode_url($outfile); + if ( $proto != "" ) // i.e. not file:// + $outfile = $file; // just save it locally, FIXME? could save it like wget: ./host/basepath/file + + $outfile = realpath(dirname($outfile)) . DIRECTORY_SEPARATOR . basename($outfile); + + if ( strpos($outfile, DOMPDF_CHROOT) !== 0 ) + throw new DOMPDF_Exception("Permission denied."); + + file_put_contents($outfile, $dompdf->output( array("compress" => 0) )); + exit(0); +} + +if ( !headers_sent() ) { + $dompdf->stream($outfile, $options); +} diff --git a/library/vendor/dompdf/dompdf_config.custom.inc.php b/library/vendor/dompdf/dompdf_config.custom.inc.php new file mode 100644 index 000000000..4e58b4a92 --- /dev/null +++ b/library/vendor/dompdf/dompdf_config.custom.inc.php @@ -0,0 +1,31 @@ + + * @author Helmut Tischer + * @author Fabien Ménager + * @autho Brian Sweeney + * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + */ + +if ( class_exists( 'DOMPDF' , false ) ) { return; } + +PHP_VERSION >= 5.0 or die("DOMPDF requires PHP 5.0+"); + +/** + * The root of your DOMPDF installation + */ +define("DOMPDF_DIR", str_replace(DIRECTORY_SEPARATOR, '/', realpath(dirname(__FILE__)))); + +/** + * The location of the DOMPDF include directory + */ +define("DOMPDF_INC_DIR", DOMPDF_DIR . "/include"); + +/** + * The location of the DOMPDF lib directory + */ +define("DOMPDF_LIB_DIR", DOMPDF_DIR . "/lib"); + +/** + * Some installations don't have $_SERVER['DOCUMENT_ROOT'] + * http://fyneworks.blogspot.com/2007/08/php-documentroot-in-iis-windows-servers.html + */ +if( !isset($_SERVER['DOCUMENT_ROOT']) ) { + $path = ""; + + if ( isset($_SERVER['SCRIPT_FILENAME']) ) + $path = $_SERVER['SCRIPT_FILENAME']; + elseif ( isset($_SERVER['PATH_TRANSLATED']) ) + $path = str_replace('\\\\', '\\', $_SERVER['PATH_TRANSLATED']); + + $_SERVER['DOCUMENT_ROOT'] = str_replace( '\\', '/', substr($path, 0, 0-strlen($_SERVER['PHP_SELF']))); +} + +/** Include the custom config file if it exists */ +if ( file_exists(DOMPDF_DIR . "/dompdf_config.custom.inc.php") ){ + require_once(DOMPDF_DIR . "/dompdf_config.custom.inc.php"); +} + +//FIXME: Some function definitions rely on the constants defined by DOMPDF. However, might this location prove problematic? +require_once(DOMPDF_INC_DIR . "/functions.inc.php"); + +/** + * Username and password used by the configuration utility in www/ + */ +def("DOMPDF_ADMIN_USERNAME", "user"); +def("DOMPDF_ADMIN_PASSWORD", "password"); + +/** + * 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 webserver process. + * *Please note the trailing slash.* + * + * Notes regarding fonts: + * Additional .afm font metrics can be added by executing load_font.php from command line. + * + * Only the original "Base 14 fonts" are present on all pdf viewers. Additional fonts must + * be embedded in the pdf file or the PDF may not display correctly. This can significantly + * increase file size unless font subsetting is enabled. Before embedding a font please + * review your rights under the font license. + * + * Any font specification in the source HTML is translated to the closest font available + * in the font directory. + * + * The pdf standard "Base 14 fonts" are: + * Courier, Courier-Bold, Courier-BoldOblique, Courier-Oblique, + * Helvetica, Helvetica-Bold, Helvetica-BoldOblique, Helvetica-Oblique, + * Times-Roman, Times-Bold, Times-BoldItalic, Times-Italic, + * Symbol, ZapfDingbats. + */ +def("DOMPDF_FONT_DIR", DOMPDF_DIR . "/lib/fonts/"); + +/** + * 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 DOMPDF_FONT_DIR + * + * Note: This directory must exist and be writable by the webserver process. + */ +def("DOMPDF_FONT_CACHE", DOMPDF_FONT_DIR); + +/** + * The location of a temporary directory. + * + * The directory specified must be writeable by the webserver process. + * The temporary directory is required to download remote images and when + * using the PFDLib back end. + */ +def("DOMPDF_TEMP_DIR", sys_get_temp_dir()); + +/** + * ==== IMPORTANT ==== + * + * dompdf's "chroot": Prevents dompdf from accessing system files or other + * files on the webserver. All local files opened by dompdf must be in a + * subdirectory of this directory. DO NOT set it to '/' since this could + * allow an attacker to use dompdf to read any files on the server. This + * should be an absolute path. + * This is only checked on command line call by dompdf.php, but not by + * direct class use like: + * $dompdf = new DOMPDF(); $dompdf->load_html($htmldata); $dompdf->render(); $pdfdata = $dompdf->output(); + */ +def("DOMPDF_CHROOT", realpath(DOMPDF_DIR)); + +/** + * Whether to use Unicode fonts or not. + * + * When set to true the PDF backend must be set to "CPDF" and fonts must be + * loaded via load_font.php. + * + * When enabled, dompdf can support all Unicode glyphs. Any glyphs used in a + * document must be present in your fonts, however. + */ +def("DOMPDF_UNICODE_ENABLED", true); + +/** + * Whether to enable font subsetting or not. + */ +def("DOMPDF_ENABLE_FONTSUBSETTING", false); + +/** + * The PDF rendering backend to use + * + * Valid settings are 'PDFLib', 'CPDF' (the bundled R&OS PDF class), '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 + * Canvas_Factory} ultimately determines which rendering class to instantiate + * based on this setting. + * + * Both PDFLib & CPDF rendering backends provide sufficient rendering + * capabilities for dompdf, however additional features (e.g. object, + * image and font support, etc.) differ between backends. Please see + * {@link PDFLib_Adapter} for more information on the PDFLib backend + * and {@link CPDF_Adapter} and lib/class.pdf.php for more information + * on CPDF. Also see the documentation for each backend at the links + * below. + * + * The GD rendering backend is a little different than PDFLib and + * CPDF. Several features of CPDF and PDFLib are not supported or do + * not make any sense when creating image files. For example, + * multiple pages are not supported, nor are PDF 'objects'. Have a + * look at {@link GD_Adapter} for more information. GD support is + * experimental, so use it at your own risk. + * + * @link http://www.pdflib.com + * @link http://www.ros.co.nz/pdf + * @link http://www.php.net/image + */ +def("DOMPDF_PDF_BACKEND", "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 explicitely above, + * a real license code must exist! + */ +//def("DOMPDF_PDFLIB_LICENSE", "your license key here"); + +/** + * html target media view which should be rendered into pdf. + * 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 + * Note: aural is deprecated in CSS 2.1 because it is replaced by speech in CSS 3. + * 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 here. + */ +def("DOMPDF_DEFAULT_MEDIA_TYPE", "screen"); + +/** + * The default paper size. + * + * North America standard is "letter"; other countries generally "a4" + * + * @see CPDF_Adapter::PAPER_SIZES for valid sizes + */ +def("DOMPDF_DEFAULT_PAPER_SIZE", "letter"); + +/** + * The default font family + * + * Used if no suitable fonts can be found. This must exist in the font folder. + * @var string + */ +def("DOMPDF_DEFAULT_FONT", "serif"); + +/** + * Image DPI setting + * + * This setting determines the default DPI setting for images and fonts. The + * DPI may be overridden for inline images by explictly 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 + * + * Rendering resolution of various browsers in px per inch: + * Windows Firefox and Internet Explorer: + * SystemControl->Display properties->FontResolution: Default:96, largefonts:120, custom:? + * Linux Firefox: + * about:config *resolution: Default:96 + * (xorg screen dimension in mm and Desktop font dpi settings are ignored) + * + * Take care about extra font/image zoom factor of browser. + * + * In images, size in pixel attribute, img css style, are overriding + * the real image dimension in px for rendering. + * + * @var int + */ +def("DOMPDF_DPI", 96); + +/** + * Enable inline PHP + * + * If this setting is set to true then DOMPDF will automatically evaluate + * inline PHP contained within tags. + * + * Enabling this for documents you do not trust (e.g. arbitrary remote html + * pages) is a security risk. Set this option to false if you wish to process + * untrusted documents. + * + * @var bool + */ +def("DOMPDF_ENABLE_PHP", false); + +/** + * Enable inline Javascript + * + * If this setting is set to true then DOMPDF will automatically insert + * JavaScript code contained within tags. + * + * @var bool + */ +def("DOMPDF_ENABLE_JAVASCRIPT", true); + +/** + * Enable remote file access + * + * If this setting is set to true, DOMPDF will access remote sites for + * images and CSS files as required. + * This is required for part of test case www/test/image_variants.html through www/examples.php + * + * Attention! + * This can be a security risk, in particular in combination with DOMPDF_ENABLE_PHP and + * allowing remote access to dompdf.php or on 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. + * + * @var bool + */ +def("DOMPDF_ENABLE_REMOTE", false); + +/** + * The debug output log + * @var string + */ +def("DOMPDF_LOG_OUTPUT_FILE", DOMPDF_FONT_DIR."log.htm"); + +/** + * A ratio applied to the fonts height to be more like browsers' line height + */ +def("DOMPDF_FONT_HEIGHT_RATIO", 1.1); + +/** + * Enable CSS float + * + * Allows people to disabled CSS float support + * @var bool + */ +def("DOMPDF_ENABLE_CSS_FLOAT", false); + +/** + * Enable the built in DOMPDF autoloader + * + * @var bool + */ +def("DOMPDF_ENABLE_AUTOLOAD", true); + +/** + * Prepend the DOMPDF autoload function to the spl_autoload stack + * + * @var bool + */ +def("DOMPDF_AUTOLOAD_PREPEND", false); + +/** + * Use the more-than-experimental HTML5 Lib parser + */ +def("DOMPDF_ENABLE_HTML5PARSER", false); +require_once(DOMPDF_LIB_DIR . "/html5lib/Parser.php"); + +// ### End of user-configurable options ### + +/** + * Load autoloader + */ +if (DOMPDF_ENABLE_AUTOLOAD) { + require_once(DOMPDF_INC_DIR . "/autoload.inc.php"); + require_once(DOMPDF_LIB_DIR . "/php-font-lib/classes/Font.php"); +} + +/** + * Ensure that PHP is working with text internally using UTF8 character encoding. + */ +mb_internal_encoding('UTF-8'); + +/** + * Global array of warnings generated by DomDocument parser and + * stylesheet class + * + * @var array + */ +global $_dompdf_warnings; +$_dompdf_warnings = array(); + +/** + * If true, $_dompdf_warnings is dumped on script termination when using + * dompdf/dompdf.php or after rendering when using the DOMPDF class. + * When using the class, setting this value to true will prevent you from + * streaming the PDF. + * + * @var bool + */ +global $_dompdf_show_warnings; +$_dompdf_show_warnings = false; + +/** + * If true, the entire tree is dumped to stdout in dompdf.cls.php. + * Setting this value to true will prevent you from streaming the PDF. + * + * @var bool + */ +global $_dompdf_debug; +$_dompdf_debug = false; + +/** + * Array of enabled debug message types + * + * @var array + */ +global $_DOMPDF_DEBUG_TYPES; +$_DOMPDF_DEBUG_TYPES = array(); //array("page-break" => 1); + +/* Optionally enable different classes of debug output before the pdf content. + * Visible if displaying pdf as text, + * E.g. on repeated display of same pdf in browser when pdf is not taken out of + * the browser cache and the premature output prevents setting of the mime type. + */ +def('DEBUGPNG', false); +def('DEBUGKEEPTEMP', false); +def('DEBUGCSS', false); + +/* Layout debugging. Will display rectangles around different block levels. + * Visible in the PDF itself. + */ +def('DEBUG_LAYOUT', false); +def('DEBUG_LAYOUT_LINES', true); +def('DEBUG_LAYOUT_BLOCKS', true); +def('DEBUG_LAYOUT_INLINE', true); +def('DEBUG_LAYOUT_PADDINGBOX', true); diff --git a/library/vendor/dompdf/include/absolute_positioner.cls.php b/library/vendor/dompdf/include/absolute_positioner.cls.php new file mode 100644 index 000000000..050db49f7 --- /dev/null +++ b/library/vendor/dompdf/include/absolute_positioner.cls.php @@ -0,0 +1,125 @@ + + * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + */ + +/** + * Positions absolutely positioned frames + */ +class Absolute_Positioner extends Positioner { + + function __construct(Frame_Decorator $frame) { parent::__construct($frame); } + + function position() { + + $frame = $this->_frame; + $style = $frame->get_style(); + + $p = $frame->find_positionned_parent(); + + list($x, $y, $w, $h) = $frame->get_containing_block(); + + $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); + + if ( $p && !($left === "auto" && $right === "auto") ) { + // Get the parent's padding box (see http://www.w3.org/TR/CSS21/visuren.html#propdef-top) + list($x, $y, $w, $h) = $p->get_padding_box(); + } + + list($width, $height) = array($frame->get_margin_width(), $frame->get_margin_height()); + + $orig_style = $this->_frame->get_original_style(); + $orig_width = $orig_style->width; + $orig_height = $orig_style->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 = $x + $frame->find_block_parent()->get_current_line_box()->w; + } + else { + if ( $orig_width === "auto" ) { + // C + $x += $w - $width - $right; + } + else { + // G + $x += $w - $width - $right; + } + } + } + else { + if ( $right === "auto" ) { + // B or F + $x += $left; + } + else { + if ( $orig_width === "auto" ) { + // D - TODO change width + $x += $left; + } + else { + // H - Everything is fixed: left + width win + $x += $left; + } + } + } + + // The same vertically + if ( $top === "auto" ) { + if ( $bottom === "auto" ) { + // A or E - Keep the frame at the same position + $y = $frame->find_block_parent()->get_current_line_box()->y; + } + else { + if ( $orig_height === "auto" ) { + // C + $y += $h - $height - $bottom; + } + else { + // G + $y += $h - $height - $bottom; + } + } + } + else { + if ( $bottom === "auto" ) { + // B or F + $y += $top; + } + else { + if ( $orig_height === "auto" ) { + // D - TODO change height + $y += $top; + } + else { + // H - Everything is fixed: top + height win + $y += $top; + } + } + } + + $frame->set_position($x, $y); + + } +} \ No newline at end of file diff --git a/library/vendor/dompdf/include/abstract_renderer.cls.php b/library/vendor/dompdf/include/abstract_renderer.cls.php new file mode 100644 index 000000000..fc27aec10 --- /dev/null +++ b/library/vendor/dompdf/include/abstract_renderer.cls.php @@ -0,0 +1,759 @@ + + * @author Helmut Tischer + * @author Fabien Ménager + * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + */ + +/** + * Base renderer class + * + * @access private + * @package dompdf + */ +abstract class Abstract_Renderer { + + /** + * Rendering backend + * + * @var 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->get_canvas(); + } + + /** + * Render a frame. + * + * Specialized in child classes + * + * @param Frame $frame The frame to render + */ + abstract function render(Frame $frame); + + //........................................................................ + + /** + * 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 (DEBUGPNG) print '[_background_image '.$url.']'; + + list($img, $type, /*$msg*/) = Image_Cache::resolve_url( + $url, + $sheet->get_protocol(), + $sheet->get_host(), + $sheet->get_base_path(), + $this->_dompdf + ); + + // Bail if the image is no good + if ( Image_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) = dompdf_getimagesize($img); + if (!isset($img_w) || $img_w == 0 || !isset($img_h) || $img_h == 0) { + return; + } + + $repeat = $style->background_repeat; + $dpi = $this->_dompdf->get_option("dpi"); + + //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); + + //Need %bg_x, $bg_y as background pos, where img starts, converted to pixel + + list($bg_x, $bg_y) = $style->background_position; + + if ( 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 + $style->length_in_pt($style->border_left_width)*$dpi / 72); + + if ( 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 + $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"; + } + + //Use filename as indicator only + //different names for different variants to have different copies in the pdf + //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! + + $filedummy = $img; + + $is_png = false; + $filedummy .= '_'.$bg_width.'_'.$bg_height.'_'.$bg_x.'_'.$bg_y.'_'.$repeat; + + //Optimization to avoid multiple times rendering the same image. + //If check functions are existing and identical image already cached, + //then skip creation of duplicate, because it is not needed by addImagePng + if ( $this->_canvas instanceof CPDF_Adapter && + $this->_canvas->get_cpdf()->image_iscached($filedummy) ) { + $bg = null; + } + + else { + + // Create a new image to fit over the background rectangle + $bg = imagecreatetruecolor($bg_width, $bg_height); + + switch (strtolower($type)) { + case IMAGETYPE_PNG: + $is_png = true; + imagesavealpha($bg, true); + imagealphablending($bg, false); + $src = imagecreatefrompng($img); + break; + + case IMAGETYPE_JPEG: + $src = imagecreatefromjpeg($img); + break; + + case IMAGETYPE_GIF: + $src = imagecreatefromgif($img); + break; + + case IMAGETYPE_BMP: + $src = imagecreatefrombmp($img); + break; + + default: + return; // Unsupported image type + } + + 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); + + if ( $ti >= 0 ) { + $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); + + } + else if ( $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); + } + + } + else if ( $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); + + } + + } + else if ( $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); + + } /* End optimize away creation of duplicates */ + + $this->_canvas->clipping_rectangle($x, $y, $box_width, $box_height); + + //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 + //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 ( !$is_png && $this->_canvas instanceof CPDF_Adapter ) { + // Note: CPDF_Adapter image converts y position + $this->_canvas->get_cpdf()->addImagePng($filedummy, $x, $this->_canvas->get_height() - $y - $height, $width, $height, $bg); + } + + else { + $tmp_dir = $this->_dompdf->get_option("temp_dir"); + $tmp_name = tempnam($tmp_dir, "bg_dompdf_img_"); + @unlink($tmp_name); + $tmp_file = "$tmp_name.png"; + + //debugpng + if (DEBUGPNG) print '[_background_image '.$tmp_file.']'; + + imagepng($bg, $tmp_file); + $this->_canvas->image($tmp_file, $x, $y, $width, $height); + imagedestroy($bg); + + //debugpng + if (DEBUGPNG) print '[_background_image unlink '.$tmp_file.']'; + + if (!DEBUGKEEPTEMP) { + unlink($tmp_file); + } + } + + $this->_canvas->clipping_end(); + } + + protected function _get_dash_pattern($style, $width) { + $pattern = array(); + + switch ($style) { + default: + /*case "solid": + case "double": + case "groove": + case "inset": + case "outset": + case "ridge":*/ + case "none": break; + + case "dotted": + if ( $width <= 1 ) + $pattern = array($width, $width*2); + else + $pattern = array($width); + break; + + case "dashed": + $pattern = array(3 * $width); + break; + } + + return $pattern; + } + + protected function _border_none($x, $y, $length, $color, $widths, $side, $corner_style = "bevel", $r1 = 0, $r2 = 0) { + return; + } + + protected function _border_hidden($x, $y, $length, $color, $widths, $side, $corner_style = "bevel", $r1 = 0, $r2 = 0) { + return; + } + + // Border rendering functions + + 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); + } + + + 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); + } + + + protected function _border_solid($x, $y, $length, $color, $widths, $side, $corner_style = "bevel", $r1 = 0, $r2 = 0) { + // TODO: Solve rendering where one corner is beveled (radius == 0), one corner isn't. + if ( $corner_style !== "bevel" || $r1 > 0 || $r2 > 0 ) { + // do it the simple way + $this->_border_line($x, $y, $length, $color, $widths, $side, $corner_style, "solid", $r1, $r2); + return; + } + + list($top, $right, $bottom, $left) = $widths; + + // All this polygon business is for beveled corners... + switch ($side) { + case "top": + $points = array($x, $y, + $x + $length, $y, + $x + $length - $right, $y + $top, + $x + $left, $y + $top); + $this->_canvas->polygon($points, $color, null, null, true); + break; + + case "bottom": + $points = array($x, $y, + $x + $length, $y, + $x + $length - $right, $y - $bottom, + $x + $left, $y - $bottom); + $this->_canvas->polygon($points, $color, null, null, true); + break; + + case "left": + $points = array($x, $y, + $x, $y + $length, + $x + $left, $y + $length - $bottom, + $x + $left, $y + $top); + $this->_canvas->polygon($points, $color, null, null, true); + break; + + case "right": + $points = array($x, $y, + $x, $y + $length, + $x - $right, $y + $length - $bottom, + $x - $right, $y + $top); + $this->_canvas->polygon($points, $color, null, null, true); + break; + + default: + return; + } + } + + 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; + + } + } + + 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 = array($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); + } + + 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 = array($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); + + } + + 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 = array($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); + + } + + protected function _tint($c) { + if ( !is_numeric($c) ) + return $c; + + return min(1, $c + 0.16); + } + + protected function _shade($c) { + if ( !is_numeric($c) ) + return $c; + + return max(0, $c - 0.33); + } + + 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(array($this, "_shade"), $color); + $this->_border_solid($x, $y, $length, $shade, $widths, $side, $corner_style, $r1, $r2); + break; + + case "bottom": + case "right": + $tint = array_map(array($this, "_tint"), $color); + $this->_border_solid($x, $y, $length, $tint, $widths, $side, $corner_style, $r1, $r2); + break; + + default: + return; + } + } + + 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(array($this, "_tint"), $color); + $this->_border_solid($x, $y, $length, $tint, $widths, $side, $corner_style, $r1, $r2); + break; + + case "bottom": + case "right": + $shade = array_map(array($this, "_shade"), $color); + $this->_border_solid($x, $y, $length, $shade, $widths, $side, $corner_style, $r1, $r2); + break; + + default: + return; + } + } + // Draws a solid, dotted, or dashed line, observing the border radius + protected function _border_line($x, $y, $length, $color, $widths, $side, $corner_style = "bevel", $pattern_name, $r1 = 0, $r2 = 0) { + list($top, $right, $bottom, $left) = $widths; + + $width = $$side; + $pattern = $this->_get_dash_pattern($pattern_name, $width); + + $half_width = $width/2; + $r1 -= $half_width; + $r2 -= $half_width; + $adjust = $r1/80; + $length -= $width; + + switch ($side) { + case "top": + $x += $half_width; + $y += $half_width; + + if ( $r1 > 0 ) { + $this->_canvas->arc($x + $r1, $y + $r1, $r1, $r1, 90-$adjust, 135+$adjust, $color, $width, $pattern); + } + + $this->_canvas->line($x + $r1, $y, $x + $length - $r2, $y, $color, $width, $pattern); + + if ( $r2 > 0 ) { + $this->_canvas->arc($x + $length - $r2, $y + $r2, $r2, $r2, 45-$adjust, 90+$adjust, $color, $width, $pattern); + } + break; + + case "bottom": + $x += $half_width; + $y -= $half_width; + + if ( $r1 > 0 ) { + $this->_canvas->arc($x + $r1, $y - $r1, $r1, $r1, 225-$adjust, 270+$adjust, $color, $width, $pattern); + } + + $this->_canvas->line($x + $r1, $y, $x + $length - $r2, $y, $color, $width, $pattern); + + if ( $r2 > 0 ) { + $this->_canvas->arc($x + $length - $r2, $y - $r2, $r2, $r2, 270-$adjust, 315+$adjust, $color, $width, $pattern); + } + break; + + case "left": + $y += $half_width; + $x += $half_width; + + if ( $r1 > 0 ) { + $this->_canvas->arc($x + $r1, $y + $r1, $r1, $r1, 135-$adjust, 180+$adjust, $color, $width, $pattern); + } + + $this->_canvas->line($x, $y + $r1, $x, $y + $length - $r2, $color, $width, $pattern); + + if ( $r2 > 0 ) { + $this->_canvas->arc($x + $r2, $y + $length - $r2, $r2, $r2, 180-$adjust, 225+$adjust, $color, $width, $pattern); + } + break; + + case "right": + $y += $half_width; + $x -= $half_width; + + if ( $r1 > 0 ) { + $this->_canvas->arc($x - $r1, $y + $r1, $r1, $r1, 0-$adjust, 45+$adjust, $color, $width, $pattern); + } + + $this->_canvas->line($x, $y + $r1, $x, $y + $length - $r2, $color, $width, $pattern); + + if ( $r2 > 0 ) { + $this->_canvas->arc($x - $r2, $y + $length - $r2, $r2, $r2, 315-$adjust, 360+$adjust, $color, $width, $pattern); + } + break; + } + } + + protected function _set_opacity($opacity) { + if ( is_numeric($opacity) && $opacity <= 1.0 && $opacity >= 0.0 ) { + $this->_canvas->set_opacity( $opacity ); + } + } + + protected function _debug_layout($box, $color = "red", $style = array()) { + $this->_canvas->rectangle($box[0], $box[1], $box[2], $box[3], CSS_Color::parse($color), 0.1, $style); + } +} diff --git a/library/vendor/dompdf/include/attribute_translator.cls.php b/library/vendor/dompdf/include/attribute_translator.cls.php new file mode 100644 index 000000000..68da668c1 --- /dev/null +++ b/library/vendor/dompdf/include/attribute_translator.cls.php @@ -0,0 +1,592 @@ + + * @author Fabien Ménager + * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + */ + +/** + * Translates HTML 4.0 attributes into CSS rules + * + * @package dompdf + */ +class Attribute_Translator { + 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 + static private $__ATTRIBUTE_LOOKUP = array( + //'caption' => array ( 'align' => '', ), + 'img' => array( + 'align' => array( + 'bottom' => 'vertical-align: baseline;', + 'middle' => 'vertical-align: middle;', + 'top' => 'vertical-align: top;', + 'left' => 'float: left;', + 'right' => 'float: right;' + ), + 'border' => 'border: %0.2F px solid;', + 'height' => 'height: %s px;', + 'hspace' => 'padding-left: %1$0.2F px; padding-right: %1$0.2F px;', + 'vspace' => 'padding-top: %1$0.2F px; padding-bottom: %1$0.2F px;', + 'width' => 'width: %s px;', + ), + 'table' => array( + 'align' => array( + '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' => array( + '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' => array( + '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' => array( + 'align' => 'text-align: %s;', + ), + 'h1' => array( + 'align' => 'text-align: %s;', + ), + 'h2' => array( + 'align' => 'text-align: %s;', + ), + 'h3' => array( + 'align' => 'text-align: %s;', + ), + 'h4' => array( + 'align' => 'text-align: %s;', + ), + 'h5' => array( + 'align' => 'text-align: %s;', + ), + 'h6' => array( + 'align' => 'text-align: %s;', + ), + 'p' => array( + 'align' => 'text-align: %s;', + ), +// 'col' => array( +// 'align' => '', +// 'valign' => '', +// ), +// 'colgroup' => array( +// 'align' => '', +// 'valign' => '', +// ), + 'tbody' => array( + 'align' => '!set_table_row_align', + 'valign' => '!set_table_row_valign', + ), + 'td' => array( + 'align' => 'text-align: %s;', + 'bgcolor' => '!set_background_color', + 'height' => 'height: %s;', + 'nowrap' => 'white-space: nowrap;', + 'valign' => 'vertical-align: %s;', + 'width' => 'width: %s;', + ), + 'tfoot' => array( + 'align' => '!set_table_row_align', + 'valign' => '!set_table_row_valign', + ), + 'th' => array( + 'align' => 'text-align: %s;', + 'bgcolor' => '!set_background_color', + 'height' => 'height: %s;', + 'nowrap' => 'white-space: nowrap;', + 'valign' => 'vertical-align: %s;', + 'width' => 'width: %s;', + ), + 'thead' => array( + 'align' => '!set_table_row_align', + 'valign' => '!set_table_row_valign', + ), + 'tr' => array( + 'align' => '!set_table_row_align', + 'bgcolor' => '!set_table_row_bgcolor', + 'valign' => '!set_table_row_valign', + ), + 'body' => array( + 'background' => 'background-image: url(%s);', + 'bgcolor' => '!set_background_color', + 'link' => '!set_body_link', + 'text' => '!set_color', + ), + 'br' => array( + 'clear' => 'clear: %s;', + ), + 'basefont' => array( + 'color' => '!set_color', + 'face' => 'font-family: %s;', + 'size' => '!set_basefont_size', + ), + 'font' => array( + 'color' => '!set_color', + 'face' => 'font-family: %s;', + 'size' => '!set_font_size', + ), + 'dir' => array( + 'compact' => 'margin: 0.5em 0;', + ), + 'dl' => array( + 'compact' => 'margin: 0.5em 0;', + ), + 'menu' => array( + 'compact' => 'margin: 0.5em 0;', + ), + 'ol' => array( + 'compact' => 'margin: 0.5em 0;', + 'start' => 'counter-reset: -dompdf-default-counter %d;', + 'type' => 'list-style-type: %s;', + ), + 'ul' => array( + 'compact' => 'margin: 0.5em 0;', + 'type' => 'list-style-type: %s;', + ), + 'li' => array( + 'type' => 'list-style-type: %s;', + 'value' => 'counter-reset: -dompdf-default-counter %d;', + ), + 'pre' => array( + 'width' => 'width: %s;', + ), + ); + + static protected $_last_basefont_size = 3; + static protected $_font_size_lookup = array( + // 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 + */ + static protected function _resolve_target(DOMNode $node, $target, $value) { + if ( $target[0] === "!" ) { + // Function call + $func = "_" . mb_substr($target, 1); + return self::$func($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[] + */ + static protected 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 + */ + static protected 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 + */ + static protected function _set_color(DOMElement $node, $value) { + $value = self::_get_valid_color($value); + return "color: $value;"; + } + + /** + * @param DOMElement $node + * @param string $value + * + * @return string + */ + static protected function _set_background_color(DOMElement $node, $value) { + $value = self::_get_valid_color($value); + return "background-color: $value;"; + } + + /** + * @param DOMElement $node + * @param string $value + * + * @return null + */ + static protected 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 + */ + static protected function _set_table_border(DOMElement $node, $value) { + $cell_list = self::get_cell_list($node); + + foreach ($cell_list as $cell) { + $style = rtrim($cell->getAttribute(self::$_style_attr)); + $style .= "; border-width: " . ($value > 0 ? 1 : 0) . "pt; border-style: inset;"; + $style = ltrim($style, ";"); + $cell->setAttribute(self::$_style_attr, $style); + } + + $style = rtrim($node->getAttribute(self::$_style_attr), ";"); + $style .= "; border-width: $value" . "px; "; + return ltrim($style, "; "); + } + + /** + * @param DOMElement $node + * @param string $value + * + * @return string + */ + static protected 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 + */ + static protected 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 + */ + static protected 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 + */ + static protected 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 + */ + static protected 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 + */ + static protected 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 + */ + static protected 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 + */ + static protected 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 + */ + static protected 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 tags. + self::$_last_basefont_size = $value; + return null; + } + + /** + * @param DOMElement $node + * @param string $value + * + * @return string + */ + static protected 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, "; "); + } +} diff --git a/library/vendor/dompdf/include/autoload.inc.php b/library/vendor/dompdf/include/autoload.inc.php new file mode 100644 index 000000000..509d3e3bc --- /dev/null +++ b/library/vendor/dompdf/include/autoload.inc.php @@ -0,0 +1,86 @@ + + * @author Fabien Ménager + * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + */ + +/** + * DOMPDF autoload function + * + * If you have an existing autoload function, add a call to this function + * from your existing __autoload() implementation. + * + * @param string $class + */ +function DOMPDF_autoload($class) { + $filename = DOMPDF_INC_DIR . "/" . mb_strtolower($class) . ".cls.php"; + + if ( is_file($filename) ) { + include_once $filename; + } +} + +// If SPL autoload functions are available (PHP >= 5.1.2) +if ( function_exists("spl_autoload_register") ) { + $autoload = "DOMPDF_autoload"; + $funcs = spl_autoload_functions(); + + // No functions currently in the stack. + if ( !DOMPDF_AUTOLOAD_PREPEND || $funcs === false ) { + spl_autoload_register($autoload); + } + + // If PHP >= 5.3 the $prepend argument is available + else if ( PHP_VERSION_ID >= 50300 ) { + spl_autoload_register($autoload, true, true); + } + + else { + // Unregister existing autoloaders... + $compat = (PHP_VERSION_ID <= 50102 && PHP_VERSION_ID >= 50100); + + foreach ($funcs as $func) { + if (is_array($func)) { + // :TRICKY: There are some compatibility issues and some + // places where we need to error out + $reflector = new ReflectionMethod($func[0], $func[1]); + if (!$reflector->isStatic()) { + throw new Exception('This function is not compatible with non-static object methods due to PHP Bug #44144.'); + } + + // Suprisingly, spl_autoload_register supports the + // Class::staticMethod callback format, although call_user_func doesn't + if ($compat) $func = implode('::', $func); + } + + spl_autoload_unregister($func); + } + + // Register the new one, thus putting it at the front of the stack... + spl_autoload_register($autoload); + + // Now, go back and re-register all of our old ones. + foreach ($funcs as $func) { + spl_autoload_register($func); + } + + // Be polite and ensure that userland autoload gets retained + if ( function_exists("__autoload") ) { + spl_autoload_register("__autoload"); + } + } +} + +else if ( !function_exists("__autoload") ) { + /** + * Default __autoload() function + * + * @param string $class + */ + function __autoload($class) { + DOMPDF_autoload($class); + } +} diff --git a/library/vendor/dompdf/include/block_frame_decorator.cls.php b/library/vendor/dompdf/include/block_frame_decorator.cls.php new file mode 100644 index 000000000..407635c0e --- /dev/null +++ b/library/vendor/dompdf/include/block_frame_decorator.cls.php @@ -0,0 +1,234 @@ + + * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + */ + +/** + * Decorates frames for block layout + * + * @access private + * @package dompdf + */ +class Block_Frame_Decorator extends Frame_Decorator { + /** + * Current line index + * + * @var int + */ + protected $_cl; + + /** + * The block's line boxes + * + * @var Line_Box[] + */ + protected $_line_boxes; + + function __construct(Frame $frame, DOMPDF $dompdf) { + parent::__construct($frame, $dompdf); + + $this->_line_boxes = array(new Line_Box($this)); + $this->_cl = 0; + } + + function reset() { + parent::reset(); + + $this->_line_boxes = array(new Line_Box($this)); + $this->_cl = 0; + } + + /** + * @return Line_Box + */ + function get_current_line_box() { + return $this->_line_boxes[$this->_cl]; + } + + /** + * @return integer + */ + function get_current_line_number() { + return $this->_cl; + } + + /** + * @return Line_Box[] + */ + function get_line_boxes() { + return $this->_line_boxes; + } + + /** + * @param integer $i + */ + function clear_line($i) { + if ( isset($this->_line_boxes[$i]) ) { + unset($this->_line_boxes[$i]); + } + } + + /** + * @param Frame $frame + */ + function add_frame_to_line(Frame $frame) { + if ( !$frame->is_in_flow() ) { + return; + } + + $style = $frame->get_style(); + + $frame->set_containing_line($this->_line_boxes[$this->_cl]); + + /* + // Adds a new line after a block, only if certain conditions are met + if ((($frame instanceof Inline_Frame_Decorator && $frame->get_node()->nodeName !== "br") || + $frame instanceof Text_Frame_Decorator && trim($frame->get_text())) && + ($frame->get_prev_sibling() && $frame->get_prev_sibling()->get_style()->display === "block" && + $this->_line_boxes[$this->_cl]->w > 0 )) { + + $this->maximize_line_height( $style->length_in_pt($style->line_height), $frame ); + $this->add_line(); + + // Add each child of the inline frame to the line individually + foreach ($frame->get_children() as $child) + $this->add_frame_to_line( $child ); + } + else*/ + + // Handle inline frames (which are effectively wrappers) + if ( $frame instanceof Inline_Frame_Decorator ) { + + // Handle line breaks + if ( $frame->get_node()->nodeName === "br" ) { + $this->maximize_line_height( $style->length_in_pt($style->line_height), $frame ); + $this->add_line(true); + } + + return; + } + + // Trim leading text if this is an empty line. Kinda a hack to put it here, + // but what can you do... + if ( $this->get_current_line_box()->w == 0 && + $frame->is_text_node() && + !$frame->is_pre() ) { + + $frame->set_text( ltrim($frame->get_text()) ); + $frame->recalculate_width(); + } + + $w = $frame->get_margin_width(); + + if ( $w == 0 ) { + return; + } + + // Debugging code: + /* + pre_r("\n

Adding frame to line:

"); + + // pre_r("Me: " . $this->get_node()->nodeName . " (" . spl_object_hash($this->get_node()) . ")"); + // pre_r("Node: " . $frame->get_node()->nodeName . " (" . spl_object_hash($frame->get_node()) . ")"); + if ( $frame->is_text_node() ) + pre_r('"'.$frame->get_node()->nodeValue.'"'); + + pre_r("Line width: " . $this->_line_boxes[$this->_cl]->w); + pre_r("Frame: " . get_class($frame)); + pre_r("Frame width: " . $w); + pre_r("Frame height: " . $frame->get_margin_height()); + pre_r("Containing block width: " . $this->get_containing_block("w")); + */ + // End debugging + + $line = $this->_line_boxes[$this->_cl]; + if ( $line->left + $line->w + $line->right + $w > $this->get_containing_block("w")) { + $this->add_line(); + } + + $frame->position(); + + $current_line = $this->_line_boxes[$this->_cl]; + $current_line->add_frame($frame); + + if ( $frame->is_text_node() ) { + $current_line->wc += count(preg_split("/\s+/", trim($frame->get_text()))); + } + + $this->increase_line_width($w); + + $this->maximize_line_height($frame->get_margin_height(), $frame); + } + + function remove_frames_from_line(Frame $frame) { + // Search backwards through the lines for $frame + $i = $this->_cl; + $j = null; + + while ($i >= 0) { + if ( ($j = in_array($frame, $this->_line_boxes[$i]->get_frames(), true)) !== false ) { + break; + } + + $i--; + } + + if ( $j === false ) { + return; + } + + // Remove $frame and all frames that follow + while ($j < count($this->_line_boxes[$i]->get_frames())) { + $frames = $this->_line_boxes[$i]->get_frames(); + $f = $frames[$j]; + $frames[$j] = null; + unset($frames[$j]); + $j++; + $this->_line_boxes[$i]->w -= $f->get_margin_width(); + } + + // Recalculate the height of the line + $h = 0; + foreach ($this->_line_boxes[$i]->get_frames() as $f) { + $h = max( $h, $f->get_margin_height() ); + } + + $this->_line_boxes[$i]->h = $h; + + // Remove all lines that follow + while ($this->_cl > $i) { + $this->_line_boxes[ $this->_cl ] = null; + unset($this->_line_boxes[ $this->_cl ]); + $this->_cl--; + } + } + + function increase_line_width($w) { + $this->_line_boxes[ $this->_cl ]->w += $w; + } + + function maximize_line_height($val, Frame $frame) { + if ( $val > $this->_line_boxes[ $this->_cl ]->h ) { + $this->_line_boxes[ $this->_cl ]->tallest_frame = $frame; + $this->_line_boxes[ $this->_cl ]->h = $val; + } + } + + function add_line($br = false) { + +// if ( $this->_line_boxes[$this->_cl]["h"] == 0 ) //count($this->_line_boxes[$i]["frames"]) == 0 || +// return; + + $this->_line_boxes[$this->_cl]->br = $br; + $y = $this->_line_boxes[$this->_cl]->y + $this->_line_boxes[$this->_cl]->h; + + $new_line = new Line_Box($this, $y); + + $this->_line_boxes[ ++$this->_cl ] = $new_line; + } + + //........................................................................ +} diff --git a/library/vendor/dompdf/include/block_frame_reflower.cls.php b/library/vendor/dompdf/include/block_frame_reflower.cls.php new file mode 100644 index 000000000..bbbdba96d --- /dev/null +++ b/library/vendor/dompdf/include/block_frame_reflower.cls.php @@ -0,0 +1,805 @@ + + * @author Fabien Ménager + * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + */ + +/** + * Reflows block frames + * + * @access private + * @package dompdf + */ +class Block_Frame_Reflower extends Frame_Reflower { + // Minimum line width to justify, as fraction of available width + const MIN_JUSTIFY_WIDTH = 0.80; + + /** + * @var Block_Frame_Decorator + */ + protected $_frame; + + function __construct(Block_Frame_Decorator $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(); + $w = $frame->get_containing_block("w"); + + if ( $style->position === "fixed" ) { + $w = $frame->get_parent()->get_containing_block("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 = array($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 ( $frame->is_absolute() ) { + $absolute = true; + $dims[] = $left !== "auto" ? $left : 0; + $dims[] = $right !== "auto" ? $right : 0; + } + else { + $absolute = false; + } + + $sum = $style->length_in_pt($dims, $w); + + // Compare to the containing block + $diff = $w - $sum; + + if ( $diff > 0 ) { + + if ( $absolute ) { + + // resolve auto properties: see + // http://www.w3.org/TR/CSS21/visudet.html#abs-non-replaced-width + + if ( $width === "auto" && $left === "auto" && $right === "auto" ) { + + if ( $lm === "auto" ) $lm = 0; + if ( $rm === "auto" ) $rm = 0; + + // Technically, the width should be "shrink-to-fit" i.e. based on the + // preferred width of the content... a little too costly here as a + // special case. Just get the width to take up the slack: + $left = 0; + $right = 0; + $width = $diff; + } + else if ( $width === "auto" ) { + + if ( $lm === "auto" ) $lm = 0; + if ( $rm === "auto" ) $rm = 0; + if ( $left === "auto" ) $left = 0; + if ( $right === "auto" ) $right = 0; + + $width = $diff; + } + else if ( $left === "auto" ) { + + if ( $lm === "auto" ) $lm = 0; + if ( $rm === "auto" ) $rm = 0; + if ( $right === "auto" ) $right = 0; + + $left = $diff; + } + else if ( $right === "auto" ) { + + if ( $lm === "auto" ) $lm = 0; + if ( $rm === "auto" ) $rm = 0; + + $right = $diff; + } + + } + else { + + // Find auto properties and get them to take up the slack + if ( $width === "auto" ) { + $width = $diff; + } + else if ( $lm === "auto" && $rm === "auto" ) { + $lm = $rm = round($diff / 2); + } + else if ( $lm === "auto" ) { + $lm = $diff; + } + else if ( $rm === "auto" ) { + $rm = $diff; + } + } + + } + else if ($diff < 0) { + + // We are over constrained--set margin-right to the difference + $rm = $diff; + + } + + return array( + "width" => $width, + "margin_left" => $lm, + "margin_right" => $rm, + "left" => $left, + "right" => $right, + ); + } + + /** + * Call the above function, but resolve max/min widths + * + * @throws DOMPDF_Exception + * @return array + */ + protected function _calculate_restricted_width() { + $frame = $this->_frame; + $style = $frame->get_style(); + $cb = $frame->get_containing_block(); + + if ( $style->position === "fixed" ) { + $cb = $frame->get_root()->get_containing_block(); + } + + //if ( $style->position === "absolute" ) + // $cb = $frame->find_positionned_parent()->get_containing_block(); + + if ( !isset($cb["w"]) ) { + throw new DOMPDF_Exception("Box property calculation requires containing block width"); + } + + // Treat width 100% as auto + if ( $style->width === "100%" ) { + $width = "auto"; + } + else { + $width = $style->length_in_pt($style->width, $cb["w"]); + } + + extract($this->_calculate_width($width)); + + // Handle min/max width + $min_width = $style->length_in_pt($style->min_width, $cb["w"]); + $max_width = $style->length_in_pt($style->max_width, $cb["w"]); + + if ( $max_width !== "none" && $min_width > $max_width ) { + list($max_width, $min_width) = array($min_width, $max_width); + } + + if ( $max_width !== "none" && $width > $max_width ) { + extract($this->_calculate_width($max_width)); + } + + if ( $width < $min_width ) { + extract($this->_calculate_width($min_width)); + } + + return array($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() { + $lines = $this->_frame->get_line_boxes(); + $height = 0; + + foreach ($lines as $line) { + $height += $line->h; + } + + /* + $first_line = reset($lines); + $last_line = end($lines); + $height2 = $last_line->y + $last_line->h - $first_line->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"]); + + $top = $style->length_in_pt($style->top, $cb["h"]); + $bottom = $style->length_in_pt($style->bottom, $cb["h"]); + + $margin_top = $style->length_in_pt($style->margin_top, $cb["h"]); + $margin_bottom = $style->length_in_pt($style->margin_bottom, $cb["h"]); + + if ( $frame->is_absolute() ) { + + // see http://www.w3.org/TR/CSS21/visudet.html#abs-non-replaced-height + + $dims = array($top !== "auto" ? $top : 0, + $style->margin_top !== "auto" ? $style->margin_top : 0, + $style->padding_top, + $style->border_top_width, + $height !== "auto" ? $height : 0, + $style->border_bottom_width, + $style->padding_bottom, + $style->margin_bottom !== "auto" ? $style->margin_bottom : 0, + $bottom !== "auto" ? $bottom : 0); + + $sum = $style->length_in_pt($dims, $cb["h"]); + + $diff = $cb["h"] - $sum; + + if ( $diff > 0 ) { + + if ( $height === "auto" && $top === "auto" && $bottom === "auto" ) { + + if ( $margin_top === "auto" ) $margin_top = 0; + if ( $margin_bottom === "auto" ) $margin_bottom = 0; + + $height = $diff; + } + else if ( $height === "auto" && $top === "auto" ) { + + if ( $margin_top === "auto" ) $margin_top = 0; + if ( $margin_bottom === "auto" ) $margin_bottom = 0; + + $height = $content_height; + $top = $diff - $content_height; + } + else if ( $height === "auto" && $bottom === "auto" ) { + + if ( $margin_top === "auto" ) $margin_top = 0; + if ( $margin_bottom === "auto" ) $margin_bottom = 0; + + $height = $content_height; + $bottom = $diff - $content_height; + } + else if ( $top === "auto" && $bottom === "auto" ) { + + if ( $margin_top === "auto" ) $margin_top = 0; + if ( $margin_bottom === "auto" ) $margin_bottom = 0; + + $bottom = $diff; + } + else if ( $top === "auto" ) { + + if ( $margin_top === "auto" ) $margin_top = 0; + if ( $margin_bottom === "auto" ) $margin_bottom = 0; + + $top = $diff; + } + else if ( $height === "auto" ) { + + if ( $margin_top === "auto" ) $margin_top = 0; + if ( $margin_bottom === "auto" ) $margin_bottom = 0; + + $height = $diff; + } + else if ( $bottom === "auto" ) { + + if ( $margin_top === "auto" ) $margin_top = 0; + if ( $margin_bottom === "auto" ) $margin_bottom = 0; + + $bottom = $diff; + } + else { + + if ( $style->overflow === "visible" ) { + // set all autos to zero + if ( $margin_top === "auto" ) $margin_top = 0; + if ( $margin_bottom === "auto" ) $margin_bottom = 0; + if ( $top === "auto" ) $top = 0; + if ( $bottom === "auto" ) $bottom = 0; + if ( $height === "auto" ) $height = $content_height; + } + + // FIXME: overflow hidden + } + + } + + } + else { + + // Expand the height if overflow is visible + if ( $height === "auto" && $content_height > $height /* && $style->overflow === "visible" */) { + $height = $content_height; + } + + // FIXME: this should probably be moved to a seperate function as per + // _calculate_restricted_width + + // Only handle min/max height if the height is independent of the frame's content + if ( !($style->overflow === "visible" || + ($style->overflow === "hidden" && $height === "auto")) ) { + + $min_height = $style->min_height; + $max_height = $style->max_height; + + if ( isset($cb["h"]) ) { + $min_height = $style->length_in_pt($min_height, $cb["h"]); + $max_height = $style->length_in_pt($max_height, $cb["h"]); + + } + else if ( isset($cb["w"]) ) { + + if ( mb_strpos($min_height, "%") !== false ) { + $min_height = 0; + } + else { + $min_height = $style->length_in_pt($min_height, $cb["w"]); + } + + if ( mb_strpos($max_height, "%") !== false ) { + $max_height = "none"; + } + else { + $max_height = $style->length_in_pt($max_height, $cb["w"]); + } + } + + if ( $max_height !== "none" && $min_height > $max_height ) { + // Swap 'em + list($max_height, $min_height) = array($min_height, $max_height); + } + + if ( $max_height !== "none" && $height > $max_height ) { + $height = $max_height; + } + + if ( $height < $min_height ) { + $height = $min_height; + } + } + + } + + return array($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 = $style->length_in_pt($style->width, $w); + + switch ($style->text_align) { + default: + case "left": + foreach ($this->_frame->get_line_boxes() as $line) { + if ( !$line->left ) { + continue; + } + + foreach($line->get_frames() as $frame) { + if ( $frame instanceof Block_Frame_Decorator) { + continue; + } + $frame->set_position( $frame->get_position("x") + $line->left ); + } + } + return; + + case "right": + foreach ($this->_frame->get_line_boxes() as $line) { + // Move each child over by $dx + $dx = $width - $line->w - $line->right; + + foreach($line->get_frames() as $frame) { + // Block frames are not aligned by text-align + if ($frame instanceof Block_Frame_Decorator) { + continue; + } + + $frame->set_position( $frame->get_position("x") + $dx ); + } + } + break; + + + case "justify": + // We justify all lines except the last one + $lines = $this->_frame->get_line_boxes(); // needs to be a variable (strict standards) + array_pop($lines); + + foreach($lines as $i => $line) { + if ( $line->br ) { + unset($lines[$i]); + } + } + + // One space character's width. Will be used to get a more accurate spacing + $space_width = Font_Metrics::get_text_width(" ", $style->font_family, $style->font_size); + + foreach ($lines as $line) { + if ( $line->left ) { + foreach ( $line->get_frames() as $frame ) { + if ( !$frame instanceof Text_Frame_Decorator ) { + continue; + } + + $frame->set_position( $frame->get_position("x") + $line->left ); + } + } + + // Only set the spacing if the line is long enough. This is really + // just an aesthetic choice ;) + //if ( $line["left"] + $line["w"] + $line["right"] > self::MIN_JUSTIFY_WIDTH * $width ) { + + // Set the spacing for each child + if ( $line->wc > 1 ) { + $spacing = ($width - ($line->left + $line->w + $line->right) + $space_width) / ($line->wc - 1); + } + else { + $spacing = 0; + } + + $dx = 0; + foreach($line->get_frames() as $frame) { + if ( !$frame instanceof Text_Frame_Decorator ) { + continue; + } + + $text = $frame->get_text(); + $spaces = mb_substr_count($text, " "); + + $char_spacing = $style->length_in_pt($style->letter_spacing); + $_spacing = $spacing + $char_spacing; + + $frame->set_position( $frame->get_position("x") + $dx ); + $frame->set_text_spacing($_spacing); + + $dx += $spaces * $_spacing; + } + + // The line (should) now occupy the entire width + $line->w = $width; + + //} + } + break; + + case "center": + case "centre": + foreach ($this->_frame->get_line_boxes() as $line) { + // Centre each line by moving each frame in the line by: + $dx = ($width + $line->left - $line->w - $line->right ) / 2; + + foreach ($line->get_frames() as $frame) { + // Block frames are not aligned by text-align + if ($frame instanceof Block_Frame_Decorator) { + continue; + } + + $frame->set_position( $frame->get_position("x") + $dx ); + } + } + break; + } + } + + /** + * Align inline children vertically. + * Aligns each child vertically after each line is reflowed + */ + function vertical_align() { + + $canvas = null; + + foreach ( $this->_frame->get_line_boxes() as $line ) { + + $height = $line->h; + + foreach ( $line->get_frames() as $frame ) { + $style = $frame->get_style(); + + if ( $style->display !== "inline" ) { + continue; + } + + $align = $frame->get_parent()->get_style()->vertical_align; + + if ( !isset($canvas) ) { + $canvas = $frame->get_root()->get_dompdf()->get_canvas(); + } + + $baseline = $canvas->get_font_baseline($style->font_family, $style->font_size); + $y_offset = 0; + + switch ($align) { + case "baseline": + $y_offset = $height*0.8 - $baseline; // The 0.8 ratio is arbitrary until we find it's meaning + break; + + case "middle": + $y_offset = ($height*0.8 - $baseline) / 2; + break; + + case "sub": + $y_offset = 0.3 * $height; + break; + + case "super": + $y_offset = -0.2 * $height; + 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; + } + + if ( $y_offset ) { + $frame->move(0, $y_offset); + } + } + } + } + + /** + * @param Frame $child + */ + function process_clear(Frame $child){ + $enable_css_float = $this->get_dompdf()->get_option("enable_css_float"); + if ( !$enable_css_float ) { + return; + } + + $child_style = $child->get_style(); + $root = $this->_frame->get_root(); + + // Handle "clear" + if ( $child_style->clear !== "none" ) { + $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 Frame $child + * @param float $cb_x + * @param float $cb_w + */ + function process_float(Frame $child, $cb_x, $cb_w){ + $enable_css_float = $this->_frame->get_dompdf()->get_option("enable_css_float"); + if ( !$enable_css_float ) { + return; + } + + $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 Text_Frame_Decorator) { + $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 Frame_Decorator $block + */ + function reflow(Block_Frame_Decorator $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; + } + + // Generated content + $this->_set_content(); + + // Collapse margins if required + $this->_collapse_margins(); + + $style = $this->_frame->get_style(); + $cb = $this->_frame->get_containing_block(); + + if ( $style->position === "fixed" ) { + $cb = $this->_frame->get_root()->get_containing_block(); + } + + // Determine the constraints imposed by this frame: calculate the width + // of the content area: + list($w, $left_margin, $right_margin, $left, $right) = $this->_calculate_restricted_width(); + + // Store the calculated properties + $style->width = $w . "pt"; + $style->margin_left = $left_margin."pt"; + $style->margin_right = $right_margin."pt"; + $style->left = $left ."pt"; + $style->right = $right . "pt"; + + // Update the position + $this->_frame->position(); + list($x, $y) = $this->_frame->get_position(); + + // Adjust the first line based on the text-indent property + $indent = $style->length_in_pt($style->text_indent, $cb["w"]); + $this->_frame->increase_line_width($indent); + + // Determine the content edge + $top = $style->length_in_pt(array($style->margin_top, + $style->padding_top, + $style->border_top_width), $cb["h"]); + + $bottom = $style->length_in_pt(array($style->border_bottom_width, + $style->margin_bottom, + $style->padding_bottom), $cb["h"]); + + $cb_x = $x + $left_margin + $style->length_in_pt(array($style->border_left_width, + $style->padding_left), $cb["w"]); + + $cb_y = $y + $top; + + $cb_h = ($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 ) { + + // Bail out if the page is full + if ( $page->is_full() ) { + break; + } + + $child->set_containing_block($cb_x, $cb_y, $w, $cb_h); + + $this->process_clear($child); + + $child->reflow($this->_frame); + + // Don't add the child to the line if a page break has occurred + if ( $page->check_page_break($child) ) { + break; + } + + $this->process_float($child, $cb_x, $w); + } + + // Determine our height + list($height, $margin_top, $margin_bottom, $top, $bottom) = $this->_calculate_restricted_height(); + $style->height = $height; + $style->margin_top = $margin_top; + $style->margin_bottom = $margin_bottom; + $style->top = $top; + $style->bottom = $bottom; + + $needs_reposition = ($style->position === "absolute" && ($style->right !== "auto" || $style->bottom !== "auto")); + + // Absolute positioning measurement + if ( $needs_reposition ) { + $orig_style = $this->_frame->get_original_style(); + if ( $orig_style->width === "auto" && ($orig_style->left === "auto" || $orig_style->right === "auto") ) { + $width = 0; + foreach ($this->_frame->get_line_boxes() as $line) { + $width = max($line->w, $width); + } + $style->width = $width; + } + + $style->left = $orig_style->left; + $style->right = $orig_style->right; + } + + $this->_text_align(); + $this->vertical_align(); + + // Absolute positioning + if ( $needs_reposition ) { + list($x, $y) = $this->_frame->get_position(); + $this->_frame->position(); + list($new_x, $new_y) = $this->_frame->get_position(); + $this->_frame->move($new_x-$x, $new_y-$y, true); + } + + if ( $block && $this->_frame->is_in_flow() ) { + $block->add_frame_to_line($this->_frame); + + // May be inline-block + if ( $style->display === "block" ) { + $block->add_line(); + } + } + } +} diff --git a/library/vendor/dompdf/include/block_positioner.cls.php b/library/vendor/dompdf/include/block_positioner.cls.php new file mode 100644 index 000000000..d7e1c3fbc --- /dev/null +++ b/library/vendor/dompdf/include/block_positioner.cls.php @@ -0,0 +1,57 @@ + + * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + */ + +/** + * Positions block frames + * + * @access private + * @package dompdf + */ +class Block_Positioner extends Positioner { + + + function __construct(Frame_Decorator $frame) { parent::__construct($frame); } + + //........................................................................ + + function position() { + $frame = $this->_frame; + $style = $frame->get_style(); + $cb = $frame->get_containing_block(); + $p = $frame->find_block_parent(); + + if ( $p ) { + $float = $style->float; + + $enable_css_float = $frame->get_dompdf()->get_option("enable_css_float"); + if ( !$enable_css_float || !$float || $float === "none" ) { + $p->add_line(true); + } + $y = $p->get_current_line_box()->y; + + } + else { + $y = $cb["y"]; + } + + $x = $cb["x"]; + + // Relative positionning + if ( $style->position === "relative" ) { + $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"]); + + $x += $left; + $y += $top; + } + + $frame->set_position($x, $y); + } +} diff --git a/library/vendor/dompdf/include/block_renderer.cls.php b/library/vendor/dompdf/include/block_renderer.cls.php new file mode 100644 index 000000000..ef42c93d5 --- /dev/null +++ b/library/vendor/dompdf/include/block_renderer.cls.php @@ -0,0 +1,230 @@ + + * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + */ + +/** + * Renders block frames + * + * @access private + * @package dompdf + */ +class Block_Renderer extends Abstract_Renderer { + + //........................................................................ + + function render(Frame $frame) { + $style = $frame->get_style(); + $node = $frame->get_node(); + + list($x, $y, $w, $h) = $frame->get_border_box(); + + $this->_set_opacity( $frame->get_opacity( $style->opacity ) ); + + if ( $node->nodeName === "body" ) { + $h = $frame->get_containing_block("h") - $style->length_in_pt(array( + $style->margin_top, + $style->border_top_width, + $style->border_bottom_width, + $style->margin_bottom), + $style->width); + } + + // Handle anchors & links + if ( $node->nodeName === "a" && $href = $node->getAttribute("href") ) { + $this->_canvas->add_link($href, $x, $y, $w, $h); + } + + // Draw our background, border and content + list($tl, $tr, $br, $bl) = $style->get_computed_border_radius($w, $h); + + if ( $tl + $tr + $br + $bl > 0 ) { + $this->_canvas->clipping_roundrectangle( $x, $y, $w, $h, $tl, $tr, $br, $bl ); + } + + if ( ($bg = $style->background_color) !== "transparent" ) { + $this->_canvas->filled_rectangle( $x, $y, $w, $h, $bg ); + } + + if ( ($url = $style->background_image) && $url !== "none" ) { + $this->_background_image($url, $x, $y, $w, $h, $style); + } + + if ( $tl + $tr + $br + $bl > 0 ) { + $this->_canvas->clipping_end(); + } + + $border_box = array($x, $y, $w, $h); + $this->_render_border($frame, $border_box); + $this->_render_outline($frame, $border_box); + + if (DEBUG_LAYOUT && DEBUG_LAYOUT_BLOCKS) { + $this->_debug_layout($frame->get_border_box(), "red"); + if (DEBUG_LAYOUT_PADDINGBOX) { + $this->_debug_layout($frame->get_padding_box(), "red", array(0.5, 0.5)); + } + } + + if (DEBUG_LAYOUT && DEBUG_LAYOUT_LINES && $frame->get_decorator()) { + foreach ($frame->get_decorator()->get_line_boxes() as $line) { + $frame->_debug_layout(array($line->x, $line->y, $line->w, $line->h), "orange"); + } + } + } + + protected function _render_border(Frame_Decorator $frame, $border_box = null, $corner_style = "bevel") { + $style = $frame->get_style(); + $bp = $style->get_border_properties(); + + if ( empty($border_box) ) { + $border_box = $frame->get_border_box(); + } + + // find the radius + $radius = $style->get_computed_border_radius($border_box[2], $border_box[3]); // w, h + + // Short-cut: If all the borders are "solid" with the same color and style, and no radius, we'd better draw a rectangle + if ( + in_array($bp["top"]["style"], array("solid", "dashed", "dotted")) && + $bp["top"] == $bp["right"] && + $bp["right"] == $bp["bottom"] && + $bp["bottom"] == $bp["left"] && + array_sum($radius) == 0 + ) { + $props = $bp["top"]; + if ( $props["color"] === "transparent" || $props["width"] <= 0 ) return; + + list($x, $y, $w, $h) = $border_box; + $width = $style->length_in_pt($props["width"]); + $pattern = $this->_get_dash_pattern($props["style"], $width); + $this->_canvas->rectangle($x + $width / 2, $y + $width / 2, $w - $width, $h - $width, $props["color"], $width, $pattern); + return; + } + + // Do it the long way + $widths = array($style->length_in_pt($bp["top"]["width"]), + $style->length_in_pt($bp["right"]["width"]), + $style->length_in_pt($bp["bottom"]["width"]), + $style->length_in_pt($bp["left"]["width"])); + + foreach ($bp as $side => $props) { + list($x, $y, $w, $h) = $border_box; + $length = 0; + $r1 = 0; + $r2 = 0; + + if ( !$props["style"] || + $props["style"] === "none" || + $props["width"] <= 0 || + $props["color"] == "transparent" ) + continue; + + switch($side) { + case "top": + $length = $w; + $r1 = $radius["top-left"]; + $r2 = $radius["top-right"]; + break; + + case "bottom": + $length = $w; + $y += $h; + $r1 = $radius["bottom-left"]; + $r2 = $radius["bottom-right"]; + break; + + case "left": + $length = $h; + $r1 = $radius["top-left"]; + $r2 = $radius["bottom-left"]; + break; + + case "right": + $length = $h; + $x += $w; + $r1 = $radius["top-right"]; + $r2 = $radius["bottom-right"]; + break; + default: + break; + } + $method = "_border_" . $props["style"]; + + // draw rounded corners + $this->$method($x, $y, $length, $props["color"], $widths, $side, $corner_style, $r1, $r2); + } + } + + protected function _render_outline(Frame_Decorator $frame, $border_box = null, $corner_style = "bevel") { + $style = $frame->get_style(); + + $props = array( + "width" => $style->outline_width, + "style" => $style->outline_style, + "color" => $style->outline_color, + ); + + if ( !$props["style"] || $props["style"] === "none" || $props["width"] <= 0 ) + return; + + if ( empty($border_box) ) { + $border_box = $frame->get_border_box(); + } + + $offset = $style->length_in_pt($props["width"]); + $pattern = $this->_get_dash_pattern($props["style"], $offset); + + // If the outline style is "solid" we'd better draw a rectangle + if ( in_array($props["style"], array("solid", "dashed", "dotted")) ) { + $border_box[0] -= $offset / 2; + $border_box[1] -= $offset / 2; + $border_box[2] += $offset; + $border_box[3] += $offset; + + list($x, $y, $w, $h) = $border_box; + $this->_canvas->rectangle($x, $y, $w, $h, $props["color"], $offset, $pattern); + return; + } + + $border_box[0] -= $offset; + $border_box[1] -= $offset; + $border_box[2] += $offset * 2; + $border_box[3] += $offset * 2; + + $method = "_border_" . $props["style"]; + $widths = array_fill(0, 4, $props["width"]); + $sides = array("top", "right", "left", "bottom"); + $length = 0; + + foreach ($sides as $side) { + list($x, $y, $w, $h) = $border_box; + + switch($side) { + case "top": + $length = $w; + break; + + case "bottom": + $length = $w; + $y += $h; + break; + + case "left": + $length = $h; + break; + + case "right": + $length = $h; + $x += $w; + break; + default: + break; + } + + $this->$method($x, $y, $length, $props["color"], $widths, $side, $corner_style); + } + } +} diff --git a/library/vendor/dompdf/include/cached_pdf_decorator.cls.php b/library/vendor/dompdf/include/cached_pdf_decorator.cls.php new file mode 100644 index 000000000..519e572e8 --- /dev/null +++ b/library/vendor/dompdf/include/cached_pdf_decorator.cls.php @@ -0,0 +1,164 @@ + + * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + */ + +/** + * Caching canvas implementation + * + * Each rendered page is serialized and stored in the {@link Page_Cache}. + * This is useful for static forms/pages that do not need to be re-rendered + * all the time. + * + * This class decorates normal CPDF_Adapters. It is currently completely + * experimental. + * + * @access private + * @package dompdf + */ +class Cached_PDF_Decorator extends CPDF_Adapter implements Canvas { + /** + * @var CPDF_Adapter + */ + protected $_pdf; + protected $_cache_id; + protected $_current_page_id; + protected $_fonts; // fonts used in this document + + function __construct($paper = "letter", $orientation = "portrait", DOMPDF $dompdf) { + $this->_fonts = array(); + } + + /** + * Must be called after constructor + * + * @param int $cache_id + * @param CPDF_Adapter $pdf + */ + function init($cache_id, CPDF_Adapter $pdf) { + $this->_cache_id = $cache_id; + $this->_pdf = $pdf; + $this->_current_page_id = $this->_pdf->open_object(); + } + + //........................................................................ + + function get_cpdf() { return $this->_pdf->get_cpdf(); } + + function open_object() { $this->_pdf->open_object(); } + function reopen_object($object) { $this->_pdf->reopen_object($object); } + + function close_object() { $this->_pdf->close_object(); } + + function add_object($object, $where = 'all') { $this->_pdf->add_object($object, $where); } + + function serialize_object($id) { $this->_pdf->serialize_object($id); } + + function reopen_serialized_object($obj) { $this->_pdf->reopen_serialized_object($obj); } + + //........................................................................ + + function get_width() { return $this->_pdf->get_width(); } + function get_height() { return $this->_pdf->get_height(); } + function get_page_number() { return $this->_pdf->get_page_number(); } + function get_page_count() { return $this->_pdf->get_page_count(); } + + function set_page_number($num) { $this->_pdf->set_page_number($num); } + function set_page_count($count) { $this->_pdf->set_page_count($count); } + + function line($x1, $y1, $x2, $y2, $color, $width, $style = array()) { + $this->_pdf->line($x1, $y1, $x2, $y2, $color, $width, $style); + } + + function rectangle($x1, $y1, $w, $h, $color, $width, $style = array()) { + $this->_pdf->rectangle($x1, $y1, $w, $h, $color, $width, $style); + } + + function filled_rectangle($x1, $y1, $w, $h, $color) { + $this->_pdf->filled_rectangle($x1, $y1, $w, $h, $color); + } + + function polygon($points, $color, $width = null, $style = array(), $fill = false) { + $this->_pdf->polygon($points, $color, $width, $style, $fill); + } + + function circle($x, $y, $r1, $color, $width = null, $style = null, $fill = false) { + $this->_pdf->circle($x, $y, $r1, $color, $width, $style, $fill); + } + + function image($img_url, $x, $y, $w, $h, $resolution = "normal") { + $this->_pdf->image($img_url, $x, $y, $w, $h, $resolution); + } + + function text($x, $y, $text, $font, $size, $color = array(0,0,0), $word_space = 0.0, $char_space = 0.0, $angle = 0.0) { + $this->_fonts[$font] = true; + $this->_pdf->text($x, $y, $text, $font, $size, $color, $word_space, $char_space, $angle); + } + + function page_text($x, $y, $text, $font, $size, $color = array(0,0,0), $word_space = 0.0, $char_space = 0.0, $angle = 0.0) { + + // We want to remove this from cached pages since it may not be correct + $this->_pdf->close_object(); + $this->_pdf->page_text($x, $y, $text, $font, $size, $color, $word_space, $char_space, $angle); + $this->_pdf->reopen_object($this->_current_page_id); + } + + function page_script($script, $type = 'text/php') { + + // We want to remove this from cached pages since it may not be correct + $this->_pdf->close_object(); + $this->_pdf->page_script($script, $type); + $this->_pdf->reopen_object($this->_current_page_id); + } + + function new_page() { + $this->_pdf->close_object(); + + // Add the object to the current page + $this->_pdf->add_object($this->_current_page_id, "add"); + $this->_pdf->new_page(); + + Page_Cache::store_page($this->_cache_id, + $this->_pdf->get_page_number() - 1, + $this->_pdf->serialize_object($this->_current_page_id)); + + $this->_current_page_id = $this->_pdf->open_object(); + return $this->_current_page_id; + } + + function stream($filename, $options = null) { + // Store the last page in the page cache + if ( !is_null($this->_current_page_id) ) { + $this->_pdf->close_object(); + $this->_pdf->add_object($this->_current_page_id, "add"); + Page_Cache::store_page($this->_cache_id, + $this->_pdf->get_page_number(), + $this->_pdf->serialize_object($this->_current_page_id)); + Page_Cache::store_fonts($this->_cache_id, $this->_fonts); + $this->_current_page_id = null; + } + + $this->_pdf->stream($filename); + + } + + function output($options = null) { + // Store the last page in the page cache + if ( !is_null($this->_current_page_id) ) { + $this->_pdf->close_object(); + $this->_pdf->add_object($this->_current_page_id, "add"); + Page_Cache::store_page($this->_cache_id, + $this->_pdf->get_page_number(), + $this->_pdf->serialize_object($this->_current_page_id)); + $this->_current_page_id = null; + } + + return $this->_pdf->output(); + } + + function get_messages() { return $this->_pdf->get_messages(); } + +} diff --git a/library/vendor/dompdf/include/canvas.cls.php b/library/vendor/dompdf/include/canvas.cls.php new file mode 100644 index 000000000..0158df6b1 --- /dev/null +++ b/library/vendor/dompdf/include/canvas.cls.php @@ -0,0 +1,385 @@ + + * @author Fabien Ménager + * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + */ + +/** + * Main rendering interface + * + * Currently {@link CPDF_Adapter}, {@link PDFLib_Adapter}, {@link TCPDF_Adapter}, and {@link GD_Adapter} + * 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 { + function __construct($paper = "letter", $orientation = "portrait", DOMPDF $dompdf); + + /** + * @return DOMPDF + */ + function get_dompdf(); + + /** + * Returns the current page number + * + * @return int + */ + function get_page_number(); + + /** + * Returns the total number of pages + * + * @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 Style::munge_color()} for the format of the color array. + * See {@link Cpdf::setLineStyle()} for a description of the format of the + * $style parameter (aka dash). + * + * @param float $x1 + * @param float $y1 + * @param float $x2 + * @param float $y2 + * @param array $color + * @param float $width + * @param array $style + */ + function line($x1, $y1, $x2, $y2, $color, $width, $style = null); + + /** + * Draws a rectangle at x1,y1 with width w and height h + * + * See {@link Style::munge_color()} for the format of the color array. + * See {@link Cpdf::setLineStyle()} for a description of the $style + * parameter (aka dash) + * + * @param float $x1 + * @param float $y1 + * @param float $w + * @param float $h + * @param array $color + * @param float $width + * @param array $style + */ + function rectangle($x1, $y1, $w, $h, $color, $width, $style = null); + + /** + * Draws a filled rectangle at x1,y1 with width w and height h + * + * See {@link Style::munge_color()} for the format of the color array. + * + * @param float $x1 + * @param float $y1 + * @param float $w + * @param float $h + * @param array $color + */ + 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 + * + * @return + */ + function clipping_roundrectangle($x1, $y1, $w, $h, $tl, $tr, $br, $bl); + + /** + * Ends the last clipping shape + */ + function clipping_end(); + + /** + * Save current state + */ + function save(); + + /** + * Restore last state + */ + function restore(); + + /** + * Rotate + */ + function rotate($angle, $x, $y); + + /** + * Skew + */ + function skew($angle_x, $angle_y, $x, $y); + + /** + * Scale + */ + function scale($s_x, $s_y, $x, $y); + + /** + * Translate + */ + function translate($t_x, $t_y); + + /** + * Transform + */ + 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 Style::munge_color()} for the format of the color array. + * See {@link Cpdf::setLineStyle()} for a description of the $style + * parameter (aka dash) + * + * @param array $points + * @param array $color + * @param float $width + * @param array $style + * @param bool $fill Fills the polygon if true + */ + function polygon($points, $color, $width = null, $style = null, $fill = false); + + /** + * Draws a circle at $x,$y with radius $r + * + * See {@link Style::munge_color()} for the format of the color array. + * See {@link Cpdf::setLineStyle()} for a description of the $style + * parameter (aka dash) + * + * @param float $x + * @param float $y + * @param float $r + * @param array $color + * @param float $width + * @param array $style + * @param bool $fill Fills the circle if true + */ + function circle($x, $y, $r, $color, $width = null, $style = null, $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_url the path to the image + * @param float $x x position + * @param float $y y position + * @param int $w width (in pixels) + * @param int $h height (in pixels) + * @param string $resolution The resolution of the image + */ + function image($img_url, $x, $y, $w, $h, $resolution = "normal"); + + /** + * Add an arc to the PDF + * See {@link Style::munge_color()} for the format of the color array. + * + * @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 + * @param float $width + * @param array $style + * + * @return void + */ + function arc($x, $y, $r1, $r2, $astart, $aend, $color, $width, $style = array()); + + /** + * Writes text at the specified x and y coordinates + * See {@link Style::munge_color()} for the format of the color array. + * + * @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 + * @param float $word_space word spacing adjustment + * @param float $char_space char spacing adjustment + * @param float $angle angle + * + * @return void + */ + function text($x, $y, $text, $font, $size, $color = array(0,0,0), $word_space = 0.0, $char_space = 0.0, $angle = 0.0); + + /** + * Add a named destination (similar to ... 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 + * + * @return void + */ + function add_link($url, $x, $y, $width, $height); + + /** + * Add meta information to the pdf + * + * @param string $name Label of the value (Creator, Producer, etc.) + * @param string $value The text to set + */ + function add_info($name, $value); + + /** + * Calculates text size, in points + * + * @param string $text the text to be sized + * @param string $font the desired font + * @param float $size the desired font size + * @param float $word_spacing word spacing, if any + * @param float $char_spacing + * + * @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 + * @param float $size + * + * @return float + */ + function get_font_height($font, $size); + + /** + * Calculates font baseline, in points + * + * @param string $font + * @param float $size + * + * @return float + */ + function get_font_baseline($font, $size); + + /** + * Returns the font x-height, in points + * + * @param string $font + * @param float $size + * + * @return float + */ + //function get_font_x_height($font, $size); + + /** + * Sets the opacity + * + * @param float $opacity + * @param string $mode + */ + function set_opacity($opacity, $mode = "Normal"); + + /** + * 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 + * + * @return void + */ + function set_default_view($view, $options = array()); + + /** + * @param string $script + * + * @return void + */ + function javascript($script); + + /** + * Starts a new page + * + * Subsequent drawing operations will appear on the new page. + */ + function new_page(); + + /** + * Streams the PDF directly to the browser + * + * @param string $filename the name of the PDF file + * @param array $options associative array, 'Attachment' => 0 or 1, 'compress' => 1 or 0 + */ + function stream($filename, $options = null); + + /** + * Returns the PDF as a string + * + * @param array $options associative array: 'compress' => 1 or 0 + * @return string + */ + function output($options = null); +} diff --git a/library/vendor/dompdf/include/canvas_factory.cls.php b/library/vendor/dompdf/include/canvas_factory.cls.php new file mode 100644 index 000000000..ef634e6b0 --- /dev/null +++ b/library/vendor/dompdf/include/canvas_factory.cls.php @@ -0,0 +1,63 @@ + + * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + */ + +/** + * Create canvas instances + * + * The canvas factory creates canvas instances based on the + * availability of rendering backends and config options. + * + * @package dompdf + */ +class Canvas_Factory { + + /** + * Constructor is private: this is a static class + */ + private function __construct() { } + + /** + * @param DOMPDF $dompdf + * @param string|array $paper + * @param string $orientation + * @param string $class + * + * @return Canvas + */ + static function get_instance(DOMPDF $dompdf, $paper = null, $orientation = null, $class = null) { + + $backend = strtolower(DOMPDF_PDF_BACKEND); + + if ( isset($class) && class_exists($class, false) ) { + $class .= "_Adapter"; + } + + else if ( (DOMPDF_PDF_BACKEND === "auto" || $backend === "pdflib" ) && + class_exists("PDFLib", false) ) { + $class = "PDFLib_Adapter"; + } + + // FIXME The TCPDF adapter is not ready yet + //else if ( (DOMPDF_PDF_BACKEND === "auto" || $backend === "cpdf") ) + // $class = "CPDF_Adapter"; + + else if ( $backend === "tcpdf" ) { + $class = "TCPDF_Adapter"; + } + + else if ( $backend === "gd" ) { + $class = "GD_Adapter"; + } + + else { + $class = "CPDF_Adapter"; + } + + return new $class($paper, $orientation, $dompdf); + } +} diff --git a/library/vendor/dompdf/include/cellmap.cls.php b/library/vendor/dompdf/include/cellmap.cls.php new file mode 100644 index 000000000..0982849b3 --- /dev/null +++ b/library/vendor/dompdf/include/cellmap.cls.php @@ -0,0 +1,790 @@ + + * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + */ + +/** + * Maps table cells to the table grid. + * + * This class resolves borders in tables with collapsed borders and helps + * place row & column spanned table cells. + * + * @access private + * @package dompdf + */ +class Cellmap { + + /** + * Border style weight lookup for collapsed border resolution. + * + * @var array + */ + static protected $_BORDER_STYLE_SCORE = array( + "inset" => 1, + "groove" => 2, + "outset" => 3, + "ridge" => 4, + "dotted" => 5, + "dashed" => 6, + "solid" => 7, + "double" => 8, + "hidden" => 9, + "none" => 0, + ); + + /** + * The table object this cellmap is attached to. + * + * @var Table_Frame_Decorator + */ + 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 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) pairs, keyed on frame_id. + * + * @var Frame[] + */ + 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 wether the columns' width can be modified + * + * @var bool + */ + private $_columns_locked = false; + + /** + * Tells wether the table has table-layout:fixed + * + * @var bool + */ + private $_fixed_layout = false; + + //........................................................................ + + function __construct(Table_Frame_Decorator $table) { + $this->_table = $table; + $this->reset(); + } + + function __destruct() { + clear_object($this); + } + //........................................................................ + + function reset() { + $this->_num_rows = 0; + $this->_num_cols = 0; + + $this->_cells = array(); + $this->_frames = array(); + + if ( !$this->_columns_locked ) { + $this->_columns = array(); + } + + $this->_rows = array(); + + $this->_borders = array(); + + $this->__col = $this->__row = 0; + } + + //........................................................................ + + function lock_columns() { + $this->_columns_locked = true; + } + + function is_columns_locked() { + return $this->_columns_locked; + } + + function set_layout_fixed($fixed) { + $this->_fixed_layout = $fixed; + } + + function is_layout_fixed() { + return $this->_fixed_layout; + } + + function get_num_rows() { return $this->_num_rows; } + function get_num_cols() { return $this->_num_cols; } + + function &get_columns() { + return $this->_columns; + } + + function set_columns($columns) { + $this->_columns = $columns; + } + + function &get_column($i) { + if ( !isset($this->_columns[$i]) ) { + $this->_columns[$i] = array( + "x" => 0, + "min-width" => 0, + "max-width" => 0, + "used-width" => null, + "absolute" => 0, + "percent" => 0, + "auto" => true, + ); + } + + return $this->_columns[$i]; + } + + function &get_rows() { + return $this->_rows; + } + + function &get_row($j) { + if ( !isset($this->_rows[$j]) ) { + $this->_rows[$j] = array( + "y" => 0, + "first-column" => 0, + "height" => null, + ); + } + + return $this->_rows[$j]; + } + + function get_border($i, $j, $h_v, $prop = null) { + if ( !isset($this->_borders[$i][$j][$h_v]) ) { + $this->_borders[$i][$j][$h_v] = array( + "width" => 0, + "style" => "solid", + "color" => "black", + ); + } + + if ( isset($prop) ) { + return $this->_borders[$i][$j][$h_v][$prop]; + } + + return $this->_borders[$i][$j][$h_v]; + } + + function get_border_properties($i, $j) { + return array( + "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"), + ); + } + + //........................................................................ + + function get_spanned_cells(Frame $frame) { + $key = $frame->get_id(); + + if ( !isset($this->_frames[$key]) ) { + throw new DOMPDF_Exception("Frame not found in cellmap"); + } + + return $this->_frames[$key]; + + } + + function frame_exists_in_cellmap(Frame $frame) { + $key = $frame->get_id(); + return isset($this->_frames[$key]); + } + + function get_frame_position(Frame $frame) { + global $_dompdf_warnings; + + $key = $frame->get_id(); + + if ( !isset($this->_frames[$key]) ) { + throw new DOMPDF_Exception("Frame not found in cellmap"); + } + + $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 = 0; + } + else { + $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 = 0; + } + else { + $y = $this->_rows[$row]["y"]; + } + + return array($x, $y, "x" => $x, "y" => $y); + } + + function get_frame_width(Frame $frame) { + $key = $frame->get_id(); + + if ( !isset($this->_frames[$key]) ) { + throw new DOMPDF_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; + } + + function get_frame_height(Frame $frame) { + $key = $frame->get_id(); + + if ( !isset($this->_frames[$key]) ) { + throw new DOMPDF_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; + } + + + //........................................................................ + + 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"] = $next_col["x"] + $width; + } + + function set_row_height($i, $height) { + $row =& $this->get_row($i); + + if ( $row["height"] !== null && $height <= $row["height"] ) { + return; + } + + $row["height"] = $height; + $next_row =& $this->get_row($i+1); + $next_row["y"] = $row["y"] + $height; + + } + + //........................................................................ + + + protected function _resolve_border($i, $j, $h_v, $border_spec) { + $n_width = $border_spec["width"]; + $n_style = $border_spec["style"]; + + if ( !isset($this->_borders[$i][$j][$h_v]) ) { + $this->_borders[$i][$j][$h_v] = $border_spec; + return $this->_borders[$i][$j][$h_v]["width"]; + } + + $border = &$this->_borders[$i][$j][$h_v]; + + $o_width = $border["width"]; + $o_style = $border["style"]; + + if ( ($n_style === "hidden" || + $n_width > $o_width || + $o_style === "none") + + or + + ($o_width == $n_width && + in_array($n_style, self::$_BORDER_STYLE_SCORE) && + self::$_BORDER_STYLE_SCORE[ $n_style ] > self::$_BORDER_STYLE_SCORE[ $o_style ]) ) { + $border = $border_spec; + } + + return $border["width"]; + } + + //........................................................................ + + function add_frame(Frame $frame) { + + $style = $frame->get_style(); + $display = $style->display; + + $collapse = $this->_table->get_style()->border_collapse == "collapse"; + + // Recursively add the frames within tables, table-row-groups and table-rows + if ( $display === "table-row" || + $display === "table" || + $display === "inline-table" || + in_array($display, Table_Frame_Decorator::$ROW_GROUPS) ) { + + $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 ( $display !== "table-row" && $collapse ) { + + $bp = $style->get_border_properties(); + + // Resolve the 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"]); + } + + 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"]); + } + } + + + return; + } + + $node = $frame->get_node(); + + // Determine where this cell is going + $colspan = $node->getAttribute("colspan"); + $rowspan = $node->getAttribute("rowspan"); + + if ( !$colspan ) { + $colspan = 1; + $node->setAttribute("colspan",1); + } + + if ( !$rowspan ) { + $rowspan = 1; + $node->setAttribute("rowspan",1); + } + $key = $frame->get_id(); + + $bp = $style->get_border_properties(); + + + // Add the frame to the cellmap + $max_left = $max_right = 0; + + // 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 + $max_left = max($max_left, $this->_resolve_border($row, $this->__col, "vertical", $bp["left"])); + $max_right = max($max_right, $this->_resolve_border($row, $this->__col + $colspan, "vertical", $bp["right"])); + } + } + + $max_top = $max_bottom = 0; + + // Columns: + for ( $j = 0; $j < $colspan; $j++ ) { + $col = $this->__col + $j; + $this->_frames[$key]["columns"][] = $col; + + if ( $collapse ) { + // Resolve horizontal borders + $max_top = max($max_top, $this->_resolve_border($this->__row, $col, "horizontal", $bp["top"])); + $max_bottom = max($max_bottom, $this->_resolve_border($this->__row + $rowspan, $col, "horizontal", $bp["bottom"])); + } + } + + $this->_frames[$key]["frame"] = $frame; + + // Handle seperated border model + if ( !$collapse ) { + list($h, $v) = $this->_table->get_style()->border_spacing; + + // Border spacing is effectively a margin between cells + $v = $style->length_in_pt($v) / 2; + $h = $style->length_in_pt($h) / 2; + $style->margin = "$v $h"; + + // The additional 1/2 width gets added to the table proper + } + else { + // Drop the frame's actual border + $style->border_left_width = $max_left / 2; + $style->border_right_width = $max_right / 2; + $style->border_top_width = $max_top / 2; + $style->border_bottom_width = $max_bottom / 2; + $style->margin = "none"; + } + + if ( !$this->_columns_locked ) { + // Resolve the frame's width + if ( $this->_fixed_layout ) { + list($frame_min, $frame_max) = array(0, 10e-10); + } + else { + list($frame_min, $frame_max) = $frame->get_min_max_width(); + } + + $width = $style->width; + + $val = null; + if ( is_percent($width) ) { + $var = "percent"; + $val = (float)rtrim($width, "% ") / $colspan; + } + else if ( $width !== "auto" ) { + $var = "absolute"; + $val = $style->length_in_pt($frame_min) / $colspan; + } + + $min = 0; + $max = 0; + for ( $cs = 0; $cs < $colspan; $cs++ ) { + + // Resolve the frame's width(s) with other cells + $col =& $this->get_column( $this->__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 ) { + // 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) / $colspan); + for ($c = 0; $c < $colspan; $c++) { + $col =& $this->get_column($this->__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($this->__col + $c); + $col["max-width"] += $inc; + } + } + } + + $this->__col += $colspan; + if ( $this->__col > $this->_num_cols ) + $this->_num_cols = $this->__col; + + } + + //........................................................................ + + function add_row() { + + $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 + */ + function remove_row(Frame $row) { + + $key = $row->get_id(); + if ( !isset($this->_frames[$key]) ) { + return; // Presumably this row has alredy 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->_frames[$id] = null; + unset($this->_frames[$id]); + + $this->_cells[$r][$c] = null; + unset($this->_cells[$r][$c]); + } + } + + $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 + */ + function remove_row_group(Frame $group) { + + $key = $group->get_id(); + if ( !isset($this->_frames[$key]) ) { + return; // Presumably this row has alredy 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 + */ + function update_row_group(Frame $group, Frame $last_row) { + + $g_key = $group->get_id(); + $r_key = $last_row->get_id(); + + $r_rows = $this->_frames[$r_key]["rows"]; + $this->_frames[$g_key]["rows"] = range( $this->_frames[$g_key]["rows"][0], end($r_rows) ); + + } + + //........................................................................ + + function assign_x_positions() { + // 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"]; + } + + } + + function assign_frame_heights() { + // Pre-condition: widths and heights of each column & row must be + // calcluated + + foreach ( $this->_frames as $arr ) { + $frame = $arr["frame"]; + + $h = 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 Table_Cell_Frame_Decorator ) { + $frame->set_cell_height($h); + } + else { + $frame->get_style()->height = $h; + } + } + + } + + //........................................................................ + + /** + * Re-adjust frame height if the table height is larger than its content + */ + function set_frame_heights($table_height, $content_height) { + + + // Distribute the increased height proportionally amongst each row + foreach ( $this->_frames as $arr ) { + $frame = $arr["frame"]; + + $h = 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; + } + + if ( $frame instanceof Table_Cell_Frame_Decorator ) { + $frame->set_cell_height($new_height); + } + else { + $frame->get_style()->height = $new_height; + } + } + + } + + //........................................................................ + + // Used for debugging: + function __toString() { + $str = ""; + $str .= "Columns:
"; + $str .= pre_r($this->_columns, true); + $str .= "Rows:
"; + $str .= pre_r($this->_rows, true); + + $str .= "Frames:
"; + $arr = array(); + foreach ( $this->_frames as $key => $val ) { + $arr[$key] = array("columns" => $val["columns"], "rows" => $val["rows"]); + } + + $str .= pre_r($arr, true); + + if ( php_sapi_name() == "cli" ) { + $str = strip_tags(str_replace(array("
","",""), + array("\n",chr(27)."[01;33m", chr(27)."[0m"), + $str)); + } + + return $str; + } +} diff --git a/library/vendor/dompdf/include/cpdf_adapter.cls.php b/library/vendor/dompdf/include/cpdf_adapter.cls.php new file mode 100644 index 000000000..06947b505 --- /dev/null +++ b/library/vendor/dompdf/include/cpdf_adapter.cls.php @@ -0,0 +1,877 @@ + + * @author Orion Richardson + * @author Helmut Tischer + * @author Fabien Ménager + * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + */ + +// FIXME: Need to sanity check inputs to this class +require_once(DOMPDF_LIB_DIR . "/class.pdf.php"); + +/** + * PDF rendering interface + * + * CPDF_Adapter 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_Adapter implements Canvas { + + /** + * Dimensions of paper sizes in points + * + * @var array; + */ + static $PAPER_SIZES = array( + "4a0" => array(0,0,4767.87,6740.79), + "2a0" => array(0,0,3370.39,4767.87), + "a0" => array(0,0,2383.94,3370.39), + "a1" => array(0,0,1683.78,2383.94), + "a2" => array(0,0,1190.55,1683.78), + "a3" => array(0,0,841.89,1190.55), + "a4" => array(0,0,595.28,841.89), + "a5" => array(0,0,419.53,595.28), + "a6" => array(0,0,297.64,419.53), + "a7" => array(0,0,209.76,297.64), + "a8" => array(0,0,147.40,209.76), + "a9" => array(0,0,104.88,147.40), + "a10" => array(0,0,73.70,104.88), + "b0" => array(0,0,2834.65,4008.19), + "b1" => array(0,0,2004.09,2834.65), + "b2" => array(0,0,1417.32,2004.09), + "b3" => array(0,0,1000.63,1417.32), + "b4" => array(0,0,708.66,1000.63), + "b5" => array(0,0,498.90,708.66), + "b6" => array(0,0,354.33,498.90), + "b7" => array(0,0,249.45,354.33), + "b8" => array(0,0,175.75,249.45), + "b9" => array(0,0,124.72,175.75), + "b10" => array(0,0,87.87,124.72), + "c0" => array(0,0,2599.37,3676.54), + "c1" => array(0,0,1836.85,2599.37), + "c2" => array(0,0,1298.27,1836.85), + "c3" => array(0,0,918.43,1298.27), + "c4" => array(0,0,649.13,918.43), + "c5" => array(0,0,459.21,649.13), + "c6" => array(0,0,323.15,459.21), + "c7" => array(0,0,229.61,323.15), + "c8" => array(0,0,161.57,229.61), + "c9" => array(0,0,113.39,161.57), + "c10" => array(0,0,79.37,113.39), + "ra0" => array(0,0,2437.80,3458.27), + "ra1" => array(0,0,1729.13,2437.80), + "ra2" => array(0,0,1218.90,1729.13), + "ra3" => array(0,0,864.57,1218.90), + "ra4" => array(0,0,609.45,864.57), + "sra0" => array(0,0,2551.18,3628.35), + "sra1" => array(0,0,1814.17,2551.18), + "sra2" => array(0,0,1275.59,1814.17), + "sra3" => array(0,0,907.09,1275.59), + "sra4" => array(0,0,637.80,907.09), + "letter" => array(0,0,612.00,792.00), + "legal" => array(0,0,612.00,1008.00), + "ledger" => array(0,0,1224.00, 792.00), + "tabloid" => array(0,0,792.00, 1224.00), + "executive" => array(0,0,521.86,756.00), + "folio" => array(0,0,612.00,936.00), + "commercial #10 envelope" => array(0,0,684,297), + "catalog #10 1/2 envelope" => array(0,0,648,864), + "8.5x11" => array(0,0,612.00,792.00), + "8.5x14" => array(0,0,612.00,1008.0), + "11x17" => array(0,0,792.00, 1224.00), + ); + + /** + * The DOMPDF object + * + * @var DOMPDF + */ + private $_dompdf; + + /** + * Instance of Cpdf class + * + * @var Cpdf + */ + private $_pdf; + + /** + * PDF width, in points + * + * @var float + */ + private $_width; + + /** + * PDF height, in points + * + * @var float; + */ + private $_height; + + /** + * Current page number + * + * @var int + */ + private $_page_number; + + /** + * Total number of pages + * + * @var int + */ + private $_page_count; + + /** + * Text to display on every page + * + * @var array + */ + private $_page_text; + + /** + * Array of pages for accesing after rendering is initially complete + * + * @var array + */ + private $_pages; + + /** + * Array of temporary cached images to be deleted when processing is complete + * + * @var array + */ + private $_image_cache; + + /** + * Class constructor + * + * @param mixed $paper The size of paper to use in this PDF ({@link CPDF_Adapter::$PAPER_SIZES}) + * @param string $orientation The orientation of the document (either 'landscape' or 'portrait') + * @param DOMPDF $dompdf The DOMPDF instance + */ + function __construct($paper = "letter", $orientation = "portrait", DOMPDF $dompdf) { + if ( is_array($paper) ) { + $size = $paper; + } + else if ( isset(self::$PAPER_SIZES[mb_strtolower($paper)]) ) { + $size = self::$PAPER_SIZES[mb_strtolower($paper)]; + } + else { + $size = self::$PAPER_SIZES["letter"]; + } + + if ( mb_strtolower($orientation) === "landscape" ) { + list($size[2], $size[3]) = array($size[3], $size[2]); + } + + $this->_dompdf = $dompdf; + + $this->_pdf = new Cpdf( + $size, + $dompdf->get_option("enable_unicode"), + $dompdf->get_option("font_cache"), + $dompdf->get_option("temp_dir") + ); + + $this->_pdf->addInfo("Creator", "DOMPDF"); + $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->_page_text = array(); + + $this->_pages = array($this->_pdf->getFirstPageId()); + + $this->_image_cache = array(); + } + + function get_dompdf(){ + return $this->_dompdf; + } + + /** + * Class destructor + * + * Deletes all temporary image files + */ + function __destruct() { + foreach ($this->_image_cache as $img) { + // The file might be already deleted by 3rd party tmp cleaner, + // the file might not have been created at all + // (if image outputting commands failed) + // or because the destructor was called twice accidentally. + if (!file_exists($img)) { + continue; + } + + if (DEBUGPNG) print '[__destruct unlink '.$img.']'; + if (!DEBUGKEEPTEMP) unlink($img); + } + } + + /** + * Returns the Cpdf instance + * + * @return Cpdf + */ + function get_cpdf() { + return $this->_pdf; + } + + /** + * Add meta information to the PDF + * + * @param string $label label of the value (Creator, Producer, etc.) + * @param string $value the text to set + */ + function add_info($label, $value) { + $this->_pdf->addInfo($label, $value); + } + + /** + * Opens a new 'object' + * + * While an object is open, all drawing actions are recored 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_Adapter::close_object() + * @see CPDF_Adapter::add_object() + * + * @return int + */ + function open_object() { + $ret = $this->_pdf->openObject(); + $this->_pdf->saveState(); + return $ret; + } + + /** + * Reopens an existing 'object' + * + * @see CPDF_Adapter::open_object() + * @param int $object the ID of a previously opened object + */ + function reopen_object($object) { + $this->_pdf->reopenObject($object); + $this->_pdf->saveState(); + } + + /** + * Closes the current 'object' + * + * @see CPDF_Adapter::open_object() + */ + 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_Adapter::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 + */ + 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 + */ + function stop_object($object) { + $this->_pdf->stopObject($object); + } + + /** + * @access private + */ + function serialize_object($id) { + // Serialize the pdf object's current state for retrieval later + return $this->_pdf->serializeObject($id); + } + + /** + * @access private + */ + function reopen_serialized_object($obj) { + return $this->_pdf->restoreSerializedObject($obj); + } + + //........................................................................ + + /** + * Returns the PDF's width in points + * @return float + */ + function get_width() { return $this->_width; } + + /** + * Returns the PDF's height in points + * @return float + */ + function get_height() { return $this->_height; } + + /** + * Returns the current page number + * @return int + */ + function get_page_number() { return $this->_page_number; } + + /** + * Returns the total number of pages in the document + * @return int + */ + function get_page_count() { return $this->_page_count; } + + /** + * Sets the current page number + * + * @param int $num + */ + function set_page_number($num) { $this->_page_number = $num; } + + /** + * Sets the page count + * + * @param int $count + */ + 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); + } + + /** + * 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); + } + + /** + * 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); + } + + /** + * Sets the opacity + * + * @param $opacity + * @param $mode + */ + function set_opacity($opacity, $mode = "Normal") { + $this->_set_line_transparency($mode, $opacity); + $this->_set_fill_transparency($mode, $opacity); + } + + function set_default_view($view, $options = array()) { + array_unshift($options, $view); + call_user_func_array(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; + } + + // Canvas implementation + function line($x1, $y1, $x2, $y2, $color, $width, $style = array()) { + $this->_set_stroke_color($color); + $this->_set_line_style($width, "butt", "", $style); + + $this->_pdf->line($x1, $this->y($y1), + $x2, $this->y($y2)); + } + + function arc($x, $y, $r1, $r2, $astart, $aend, $color, $width, $style = array()) { + $this->_set_stroke_color($color); + $this->_set_line_style($width, "butt", "", $style); + + $this->_pdf->ellipse($x, $this->y($y), $r1, $r2, 0, 8, $astart, $aend, false, false, true, false); + } + + //........................................................................ + + /** + * Convert a GIF or BMP image to a PNG image + * + * @param string $image_url + * @param integer $type + * + * @throws DOMPDF_Exception + * @return string The url of the newly converted image + */ + protected function _convert_gif_bmp_to_png($image_url, $type) { + $image_type = Image_Cache::type_to_ext($type); + $func_name = "imagecreatefrom$image_type"; + + if ( !function_exists($func_name) ) { + throw new DOMPDF_Exception("Function $func_name() not found. Cannot convert $image_type image: $image_url. Please install the image PHP extension."); + } + + set_error_handler("record_warnings"); + $im = $func_name($image_url); + + if ( $im ) { + imageinterlace($im, false); + + $tmp_dir = $this->_dompdf->get_option("temp_dir"); + $tmp_name = tempnam($tmp_dir, "{$image_type}dompdf_img_"); + @unlink($tmp_name); + $filename = "$tmp_name.png"; + $this->_image_cache[] = $filename; + + imagepng($im, $filename); + imagedestroy($im); + } + else { + $filename = Image_Cache::$broken_image; + } + + restore_error_handler(); + + return $filename; + } + + function rectangle($x1, $y1, $w, $h, $color, $width, $style = array()) { + $this->_set_stroke_color($color); + $this->_set_line_style($width, "butt", "", $style); + $this->_pdf->rectangle($x1, $this->y($y1) - $h, $w, $h); + } + + function filled_rectangle($x1, $y1, $w, $h, $color) { + $this->_set_fill_color($color); + $this->_pdf->filledRectangle($x1, $this->y($y1) - $h, $w, $h); + } + + function clipping_rectangle($x1, $y1, $w, $h) { + $this->_pdf->clippingRectangle($x1, $this->y($y1) - $h, $w, $h); + } + + 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); + } + + function clipping_end() { + $this->_pdf->clippingEnd(); + } + + function save() { + $this->_pdf->saveState(); + } + + function restore() { + $this->_pdf->restoreState(); + } + + function rotate($angle, $x, $y) { + $this->_pdf->rotate($angle, $x, $y); + } + + function skew($angle_x, $angle_y, $x, $y) { + $this->_pdf->skew($angle_x, $angle_y, $x, $y); + } + + function scale($s_x, $s_y, $x, $y) { + $this->_pdf->scale($s_x, $s_y, $x, $y); + } + + function translate($t_x, $t_y) { + $this->_pdf->translate($t_x, $t_y); + } + + function transform($a, $b, $c, $d, $e, $f) { + $this->_pdf->transform(array($a, $b, $c, $d, $e, $f)); + } + + function polygon($points, $color, $width = null, $style = array(), $fill = false) { + $this->_set_fill_color($color); + $this->_set_stroke_color($color); + + // Adjust y values + for ( $i = 1; $i < count($points); $i += 2) { + $points[$i] = $this->y($points[$i]); + } + + $this->_pdf->polygon($points, count($points) / 2, $fill); + } + + function circle($x, $y, $r1, $color, $width = null, $style = null, $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), $r1, 0, 0, 8, 0, 360, 1, $fill); + } + + function image($img, $x, $y, $w, $h, $resolution = "normal") { + list($width, $height, $type) = dompdf_getimagesize($img); + + $debug_png = $this->_dompdf->get_option("debug_png"); + + if ($debug_png) print "[image:$img|$width|$height|$type]"; + + switch ($type) { + case IMAGETYPE_JPEG: + if ($debug_png) print '!!!jpg!!!'; + $this->_pdf->addJpegFromFile($img, $x, $this->y($y) - $h, $w, $h); + break; + + case IMAGETYPE_GIF: + case IMAGETYPE_BMP: + if ($debug_png) print '!!!bmp or gif!!!'; + // @todo use cache for BMP and GIF + $img = $this->_convert_gif_bmp_to_png($img, $type); + + case IMAGETYPE_PNG: + if ($debug_png) print '!!!png!!!'; + + $this->_pdf->addPngFromFile($img, $x, $this->y($y) - $h, $w, $h); + break; + + default: + if ($debug_png) print '!!!unknown!!!'; + } + } + + function text($x, $y, $text, $font, $size, $color = array(0,0,0), $word_space = 0.0, $char_space = 0.0, $angle = 0.0) { + $pdf = $this->_pdf; + + $pdf->setColor($color); + + $font .= ".afm"; + $pdf->selectFont($font); + + //Font_Metrics::get_font_height($font, $size) == + //$this->get_font_height($font, $size) == + //$this->_pdf->selectFont($font),$this->_pdf->getFontHeight($size) + //- FontBBoxheight+FontHeightOffset, scaled to $size, in pt + //$this->_pdf->getFontDescender($size) + //- Descender scaled to size + // + //$this->_pdf->fonts[$this->_pdf->currentFont] sizes: + //['FontBBox'][0] left, ['FontBBox'][1] bottom, ['FontBBox'][2] right, ['FontBBox'][3] top + //Maximum extent of all glyphs of the font from the baseline point + //['Ascender'] maximum height above baseline except accents + //['Descender'] maximum depth below baseline, negative number means below baseline + //['FontHeightOffset'] manual enhancement of .afm files to trim windows fonts. currently not used. + //Values are in 1/1000 pt for a font size of 1 pt + // + //['FontBBox'][1] should be close to ['Descender'] + //['FontBBox'][3] should be close to ['Ascender']+Accents + //in practice, FontBBox values are a little bigger + // + //The text position is referenced to the baseline, not to the lower corner of the FontBBox, + //for what the left,top corner is given. + //FontBBox spans also the background box for the text. + //If the lower corner would be used as reference point, the Descents of the glyphs would + //hang over the background box border. + //Therefore compensate only the extent above the Baseline. + // + //print '
['.$font.','.$size.','.$pdf->getFontHeight($size).','.$pdf->getFontDescender($size).','.$pdf->fonts[$pdf->currentFont]['FontBBox'][3].','.$pdf->fonts[$pdf->currentFont]['FontBBox'][1].','.$pdf->fonts[$pdf->currentFont]['FontHeightOffset'].','.$pdf->fonts[$pdf->currentFont]['Ascender'].','.$pdf->fonts[$pdf->currentFont]['Descender'].']
'; + // + //$pdf->addText($x, $this->y($y) - ($pdf->fonts[$pdf->currentFont]['FontBBox'][3]*$size)/1000, $size, $text, $angle, $word_space, $char_space); + $pdf->addText($x, $this->y($y) - $pdf->getFontHeight($size), $size, $text, $angle, $word_space, $char_space); + } + + //........................................................................ + + function javascript($code) { + $this->_pdf->addJavascript($code); + } + + //........................................................................ + + /** + * Add a named destination (similar to ... in html) + * + * @param string $anchorname The name of the named destination + */ + function add_named_dest($anchorname) { + $this->_pdf->addDestination($anchorname, "Fit"); + } + + //........................................................................ + + /** + * 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) { + + $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(rawurldecode($url), $x, $y, $x + $width, $y + $height); + } + } + + function get_text_width($text, $font, $size, $word_spacing = 0, $char_spacing = 0) { + $this->_pdf->selectFont($font); + + $unicode = $this->_dompdf->get_option("enable_unicode"); + if (!$unicode) { + $text = mb_convert_encoding($text, 'Windows-1252', 'UTF-8'); + } + + return $this->_pdf->getTextWidth($size, $text, $word_spacing, $char_spacing); + } + + function register_string_subset($font, $string) { + $this->_pdf->registerText($font, $string); + } + + function get_font_height($font, $size) { + $this->_pdf->selectFont($font); + + $ratio = $this->_dompdf->get_option("font_height_ratio"); + return $this->_pdf->getFontHeight($size) * $ratio; + } + + /*function get_font_x_height($font, $size) { + $this->_pdf->selectFont($font); + $ratio = $this->_dompdf->get_option("font_height_ratio"); + return $this->_pdf->getFontXHeight($size) * $ratio; + }*/ + + function get_font_baseline($font, $size) { + $ratio = $this->_dompdf->get_option("font_height_ratio"); + return $this->get_font_height($font, $size) / $ratio; + } + + /** + * 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. + * + * See {@link Style::munge_color()} for the format of the colour array. + * + * @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 + * @param float $word_space word spacing adjustment + * @param float $char_space char spacing adjustment + * @param float $angle angle to write the text at, measured CW starting from the x-axis + */ + function page_text($x, $y, $text, $font, $size, $color = array(0,0,0), $word_space = 0.0, $char_space = 0.0, $angle = 0.0) { + $_t = "text"; + $this->_page_text[] = compact("_t", "x", "y", "text", "font", "size", "color", "word_space", "char_space", "angle"); + } + + /** + * Processes a script on every page + * + * The variables $pdf, $PAGE_NUM, and $PAGE_COUNT are available. + * + * This function can be used to add page numbers to all pages + * after the first one, for example. + * + * @param string $code the script code + * @param string $type the language type for script + */ + function page_script($code, $type = "text/php") { + $_t = "script"; + $this->_page_text[] = compact("_t", "code", "type"); + } + + function new_page() { + $this->_page_number++; + $this->_page_count++; + + $ret = $this->_pdf->newPage(); + $this->_pages[] = $ret; + return $ret; + } + + /** + * Add text to each page after rendering is complete + */ + protected function _add_page_text() { + + if ( !count($this->_page_text) ) { + return; + } + + $page_number = 1; + $eval = null; + + foreach ($this->_pages as $pid) { + $this->reopen_object($pid); + + foreach ($this->_page_text as $pt) { + extract($pt); + + switch ($_t) { + case "text": + $text = str_replace(array("{PAGE_NUM}","{PAGE_COUNT}"), + array($page_number, $this->_page_count), $text); + $this->text($x, $y, $text, $font, $size, $color, $word_space, $char_space, $angle); + break; + + case "script": + if ( !$eval ) { + $eval = new PHP_Evaluator($this); + } + $eval->evaluate($code, array('PAGE_NUM' => $page_number, 'PAGE_COUNT' => $this->_page_count)); + break; + } + } + + $this->close_object(); + $page_number++; + } + } + + /** + * Streams the PDF directly to the browser + * + * @param string $filename the name of the PDF file + * @param array $options associative array, 'Attachment' => 0 or 1, 'compress' => 1 or 0 + */ + function stream($filename, $options = null) { + // Add page text + $this->_add_page_text(); + + $options["Content-Disposition"] = $filename; + $this->_pdf->stream($options); + } + + /** + * Returns the PDF as a string + * + * @param array $options Output options + * @return string + */ + function output($options = null) { + $this->_add_page_text(); + + $debug = isset($options["compress"]) && $options["compress"] != 1; + + return $this->_pdf->output($debug); + } + + /** + * Returns logging messages generated by the Cpdf class + * + * @return string + */ + function get_messages() { + return $this->_pdf->messages; + } +} diff --git a/library/vendor/dompdf/include/css_color.cls.php b/library/vendor/dompdf/include/css_color.cls.php new file mode 100644 index 000000000..481751db5 --- /dev/null +++ b/library/vendor/dompdf/include/css_color.cls.php @@ -0,0 +1,287 @@ + + * @author Fabien Ménager + * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + */ + +class CSS_Color { + static $cssColorNames = array( + "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", + ); + + static function parse($color) { + if ( is_array($color) ) { + // Assume the array has the right format... + // FIXME: should/could verify this. + return $color; + } + + static $cache = array(); + + $color = strtolower($color); + + if ( isset($cache[$color]) ) { + return $cache[$color]; + } + + if ( in_array($color, array("transparent", "inherit")) ) { + return $cache[$color] = $color; + } + + if ( isset(self::$cssColorNames[$color]) ) { + return $cache[$color] = self::getArray(self::$cssColorNames[$color]); + } + + $length = mb_strlen($color); + + // #rgb format + if ( $length == 4 && $color[0] === "#" ) { + return $cache[$color] = self::getArray($color[1].$color[1].$color[2].$color[2].$color[3].$color[3]); + } + + // #rrggbb format + else if ( $length == 7 && $color[0] === "#" ) { + return $cache[$color] = self::getArray(mb_substr($color, 1, 6)); + } + + // rgb( r,g,b ) / rgbaa( r,g,b,α ) format + else if ( mb_strpos($color, "rgb") !== false ) { + $i = mb_strpos($color, "("); + $j = mb_strpos($color, ")"); + + // Bad color value + if ( $i === false || $j === false ) { + return null; + } + + $triplet = explode(",", mb_substr($color, $i+1, $j-$i-1)); + + // alpha transparency + // FIXME: not currently using transparency + $alpha = 1; + if ( count( $triplet ) == 4 ) { + $alpha = (float) ( trim( array_pop( $triplet ) ) ); + // bad value, set to fully opaque + if ( $alpha > 1 || $alpha < 0 ) { + $alpha = 1; + } + } + + if ( count($triplet) != 3 ) { + return null; + } + + foreach (array_keys($triplet) as $c) { + $triplet[$c] = trim($triplet[$c]); + + if ( $triplet[$c][mb_strlen($triplet[$c]) - 1] === "%" ) { + $triplet[$c] = round($triplet[$c] * 2.55); + } + } + + return $cache[$color] = self::getArray(vsprintf("%02X%02X%02X", $triplet)); + + } + + // cmyk( c,m,y,k ) format + // http://www.w3.org/TR/css3-gcpm/#cmyk-colors + else if ( mb_strpos($color, "cmyk") !== false ) { + $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; + } + + foreach ($values as &$c) { + $c = floatval(trim($c)); + if ($c > 1.0) $c = 1.0; + if ($c < 0.0) $c = 0.0; + } + + return $cache[$color] = self::getArray($values); + } + + return null; + } + + static function getArray($color) { + $c = array(null, null, null, null, "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["hex"] = "cmyk($c[0],$c[1],$c[2],$c[3])"; + } + else { + $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["hex"] = "#$color"; + } + + return $c; + } +} diff --git a/library/vendor/dompdf/include/dompdf.cls.php b/library/vendor/dompdf/include/dompdf.cls.php new file mode 100644 index 000000000..1be1f8284 --- /dev/null +++ b/library/vendor/dompdf/include/dompdf.cls.php @@ -0,0 +1,1077 @@ + + * @author Fabien Ménager + * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + */ + +/** + * 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_Frame_Decorator 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 + * Positioner and {@link Frame_Reflower} 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 CPDF_Adapter} class provides a simple, stateless interface to + * PDF generation.) PDFLib support has now also been added, via the {@link + * PDFLib_Adapter}. + * + * + * @package dompdf + */ +class DOMPDF { + + /** + * DomDocument representing the HTML document + * + * @var DOMDocument + */ + protected $_xml; + + /** + * Frame_Tree derived from the DOM tree + * + * @var Frame_Tree + */ + protected $_tree; + + /** + * Stylesheet for the document + * + * @var Stylesheet + */ + protected $_css; + + /** + * Actual PDF renderer + * + * @var Canvas + */ + protected $_pdf; + + /** + * Desired paper size ('letter', 'legal', 'A4', etc.) + * + * @var string + */ + protected $_paper_size; + + /** + * Paper orientation ('portrait' or 'landscape') + * + * @var string + */ + protected $_paper_orientation; + + /** + * Callbacks on new page and new element + * + * @var array + */ + protected $_callbacks; + + /** + * Experimental caching capability + * + * @var string + */ + private $_cache_id; + + /** + * Base hostname + * + * Used for relative paths/urls + * @var string + */ + protected $_base_host; + + /** + * Absolute base path + * + * Used for relative paths/urls + * @var string + */ + protected $_base_path; + + /** + * Protcol used to request file (file://, http://, etc) + * + * @var string + */ + protected $_protocol; + + /** + * HTTP context created with stream_context_create() + * Will be used for file_get_contents + * + * @var resource + */ + protected $_http_context; + + /** + * Timestamp of the script start time + * + * @var int + */ + private $_start_time = null; + + /** + * The system's locale + * + * @var string + */ + private $_system_locale = null; + + /** + * Tells if the system's locale is the C standard one + * + * @var bool + */ + private $_locale_standard = false; + + /** + * The default view of the PDF in the viewer + * + * @var string + */ + private $_default_view = "Fit"; + + /** + * The default view options of the PDF in the viewer + * + * @var array + */ + private $_default_view_options = array(); + + /** + * Tells wether the DOM document is in quirksmode (experimental) + * + * @var bool + */ + private $_quirksmode = false; + + /** + * The list of built-in fonts + * + * @var array + */ + public static $native_fonts = array( + "courier", "courier-bold", "courier-oblique", "courier-boldoblique", + "helvetica", "helvetica-bold", "helvetica-oblique", "helvetica-boldoblique", + "times-roman", "times-bold", "times-italic", "times-bolditalic", + "symbol", "zapfdinbats" + ); + + private $_options = array( + // Directories + "temp_dir" => DOMPDF_TEMP_DIR, + "font_dir" => DOMPDF_FONT_DIR, + "font_cache" => DOMPDF_FONT_CACHE, + "chroot" => DOMPDF_CHROOT, + "log_output_file" => DOMPDF_LOG_OUTPUT_FILE, + + // Rendering + "default_media_type" => DOMPDF_DEFAULT_MEDIA_TYPE, + "default_paper_size" => DOMPDF_DEFAULT_PAPER_SIZE, + "default_font" => DOMPDF_DEFAULT_FONT, + "dpi" => DOMPDF_DPI, + "font_height_ratio" => DOMPDF_FONT_HEIGHT_RATIO, + + // Features + "enable_unicode" => DOMPDF_UNICODE_ENABLED, + "enable_php" => DOMPDF_ENABLE_PHP, + "enable_remote" => DOMPDF_ENABLE_REMOTE, + "enable_css_float" => DOMPDF_ENABLE_CSS_FLOAT, + "enable_javascript" => DOMPDF_ENABLE_JAVASCRIPT, + "enable_html5_parser" => DOMPDF_ENABLE_HTML5PARSER, + "enable_font_subsetting" => DOMPDF_ENABLE_FONTSUBSETTING, + + // Debug + "debug_png" => DEBUGPNG, + "debug_keep_temp" => DEBUGKEEPTEMP, + "debug_css" => DEBUGCSS, + "debug_layout" => DEBUG_LAYOUT, + "debug_layout_lines" => DEBUG_LAYOUT_LINES, + "debug_layout_blocks" => DEBUG_LAYOUT_BLOCKS, + "debug_layout_inline" => DEBUG_LAYOUT_INLINE, + "debug_layout_padding_box" => DEBUG_LAYOUT_PADDINGBOX, + + // Admin + "admin_username" => DOMPDF_ADMIN_USERNAME, + "admin_password" => DOMPDF_ADMIN_PASSWORD, + ); + + /** + * Class constructor + */ + function __construct() { + $this->_locale_standard = sprintf('%.1f', 1.0) == '1.0'; + + $this->save_locale(); + + $this->_messages = array(); + $this->_css = new Stylesheet($this); + $this->_pdf = null; + $this->_paper_size = DOMPDF_DEFAULT_PAPER_SIZE; + $this->_paper_orientation = "portrait"; + $this->_base_protocol = ""; + $this->_base_host = ""; + $this->_base_path = ""; + $this->_http_context = null; + $this->_callbacks = array(); + $this->_cache_id = null; + + $this->restore_locale(); + } + + /** + * Class destructor + */ + function __destruct() { + clear_object($this); + } + + /** + * Get the dompdf option value + * + * @param string $key + * + * @return mixed + * @throws DOMPDF_Exception + */ + function get_option($key) { + if ( !array_key_exists($key, $this->_options) ) { + throw new DOMPDF_Exception("Option '$key' doesn't exist"); + } + + return $this->_options[$key]; + } + + /** + * @param string $key + * @param mixed $value + * + * @throws DOMPDF_Exception + */ + function set_option($key, $value) { + if ( !array_key_exists($key, $this->_options) ) { + throw new DOMPDF_Exception("Option '$key' doesn't exist"); + } + + $this->_options[$key] = $value; + } + + /** + * @param array $options + */ + function set_options(array $options) { + foreach ($options as $key => $value) { + $this->set_option($key, $value); + } + } + + /** + * Save the system's locale configuration and + * set the right value for numeric formatting + */ + private function save_locale() { + if ( $this->_locale_standard ) { + return; + } + + $this->_system_locale = setlocale(LC_NUMERIC, "0"); + setlocale(LC_NUMERIC, "C"); + } + + /** + * Restore the system's locale configuration + */ + private function restore_locale() { + if ( $this->_locale_standard ) { + return; + } + + setlocale(LC_NUMERIC, $this->_system_locale); + } + + /** + * Returns the underlying {@link Frame_Tree} object + * + * @return Frame_Tree + */ + function get_tree() { + return $this->_tree; + } + + /** + * Sets the protocol to use + * FIXME validate these + * + * @param string $proto + */ + function set_protocol($proto) { + $this->_protocol = $proto; + } + + /** + * Sets the base hostname + * + * @param string $host + */ + function set_host($host) { + $this->_base_host = $host; + } + + /** + * Sets the base path + * + * @param string $path + */ + function set_base_path($path) { + $this->_base_path = $path; + } + + /** + * Sets the HTTP context + * + * @param resource $http_context + */ + function set_http_context($http_context) { + $this->_http_context = $http_context; + } + + /** + * Sets the default view + * + * @param string $default_view The default document view + * @param array $options The view's options + */ + function set_default_view($default_view, $options) { + $this->_default_view = $default_view; + $this->_default_view_options = $options; + } + + /** + * Returns the protocol in use + * + * @return string + */ + function get_protocol() { + return $this->_protocol; + } + + /** + * Returns the base hostname + * + * @return string + */ + function get_host() { + return $this->_base_host; + } + + /** + * Returns the base path + * + * @return string + */ + function get_base_path() { + return $this->_base_path; + } + + /** + * Returns the HTTP context + * + * @return resource + */ + function get_http_context() { + return $this->_http_context; + } + + /** + * Return the underlying Canvas instance (e.g. CPDF_Adapter, GD_Adapter) + * + * @return Canvas + */ + function get_canvas() { + return $this->_pdf; + } + + /** + * Returns the callbacks array + * + * @return array + */ + function get_callbacks() { + return $this->_callbacks; + } + + /** + * Returns the stylesheet + * + * @return Stylesheet + */ + function get_css() { + return $this->_css; + } + + /** + * @return DOMDocument + */ + function get_dom() { + return $this->_xml; + } + + /** + * Loads an HTML file + * Parse errors are stored in the global array _dompdf_warnings. + * + * @param string $file a filename or url to load + * + * @throws DOMPDF_Exception + */ + function load_html_file($file) { + $this->save_locale(); + + // Store parsing warnings as messages (this is to prevent output to the + // browser if the html is ugly and the dom extension complains, + // preventing the pdf from being streamed.) + if ( !$this->_protocol && !$this->_base_host && !$this->_base_path ) { + list($this->_protocol, $this->_base_host, $this->_base_path) = explode_url($file); + } + + if ( !$this->get_option("enable_remote") && ($this->_protocol != "" && $this->_protocol !== "file://" ) ) { + throw new DOMPDF_Exception("Remote file requested, but DOMPDF_ENABLE_REMOTE is false."); + } + + if ($this->_protocol == "" || $this->_protocol === "file://") { + + // Get the full path to $file, returns false if the file doesn't exist + $realfile = realpath($file); + if ( !$realfile ) { + throw new DOMPDF_Exception("File '$file' not found."); + } + + $chroot = $this->get_option("chroot"); + if ( strpos($realfile, $chroot) !== 0 ) { + throw new DOMPDF_Exception("Permission denied on $file. The file could not be found under the directory specified by DOMPDF_CHROOT."); + } + + // Exclude dot files (e.g. .htaccess) + if ( substr(basename($realfile), 0, 1) === "." ) { + throw new DOMPDF_Exception("Permission denied on $file."); + } + + $file = $realfile; + } + + $contents = file_get_contents($file, null, $this->_http_context); + $encoding = null; + + // 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->restore_locale(); + + $this->load_html($contents, $encoding); + } + + /** + * Loads an HTML string + * Parse errors are stored in the global array _dompdf_warnings. + * @todo use the $encoding variable + * + * @param string $str HTML text to load + * @param string $encoding Not used yet + */ + function load_html($str, $encoding = null) { + $this->save_locale(); + + // FIXME: Determine character encoding, switch to UTF8, update meta tag. Need better http/file stream encoding detection, currently relies on text or meta tag. + mb_detect_order('auto'); + + if (mb_detect_encoding($str) !== 'UTF-8') { + $metatags = array( + '@]*charset\s*=\s*["\']?\s*([^"\' ]+)@i', + ); + + foreach($metatags as $metatag) { + if (preg_match($metatag, $str, $matches)) break; + } + + if (mb_detect_encoding($str) == '') { + if (isset($matches[1])) { + $encoding = strtoupper($matches[1]); + } + else { + $encoding = 'UTF-8'; + } + } + else { + if ( isset($matches[1]) ) { + $encoding = strtoupper($matches[1]); + } + else { + $encoding = 'auto'; + } + } + + if ( $encoding !== 'UTF-8' ) { + $str = mb_convert_encoding($str, 'UTF-8', $encoding); + } + + if ( isset($matches[1]) ) { + $str = preg_replace('/charset=([^\s"]+)/i', 'charset=UTF-8', $str); + } + else { + $str = str_replace('', '', $str); + } + } + else { + $encoding = 'UTF-8'; + } + + // 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); + } + + // if the document contains non utf-8 with a utf-8 meta tag chars and was + // detected as utf-8 by mbstring, problems could happen. + // http://devzone.zend.com/article/8855 + if ( $encoding !== 'UTF-8' ) { + $re = '/]*)((?:charset=[^"\' ]+)([^>]*)|(?:charset=["\'][^"\' ]+["\']))([^>]*)>/i'; + $str = preg_replace($re, '', $str); + } + + // Store parsing warnings as messages + set_error_handler("record_warnings"); + + // @todo Take the quirksmode into account + // http://hsivonen.iki.fi/doctype/ + // https://developer.mozilla.org/en/mozilla's_quirks_mode + $quirksmode = false; + + if ( $this->get_option("enable_html5_parser") ) { + $tokenizer = new HTML5_Tokenizer($str); + $tokenizer->parse(); + $doc = $tokenizer->save(); + + // Remove #text children nodes in nodes that shouldn't have + $tag_names = array("html", "table", "tbody", "thead", "tfoot", "tr"); + foreach($tag_names as $tag_name) { + $nodes = $doc->getElementsByTagName($tag_name); + + foreach($nodes as $node) { + self::remove_text_nodes($node); + } + } + + $quirksmode = ($tokenizer->getTree()->getQuirksMode() > HTML5_TreeBuilder::NO_QUIRKS); + } + else { + // loadHTML assumes ISO-8859-1 unless otherwise specified, but there are + // bugs in how DOMDocument determines the actual encoding. Converting to + // HTML-ENTITIES prior to import appears to resolve the issue. + // http://devzone.zend.com/1538/php-dom-xml-extension-encoding-processing/ (see #4) + // http://stackoverflow.com/a/11310258/264628 + $doc = new DOMDocument(); + $doc->preserveWhiteSpace = true; + $doc->loadHTML( mb_convert_encoding( $str , 'HTML-ENTITIES' , 'UTF-8' ) ); + + // If some text is before the doctype, we are in quirksmode + if ( preg_match("/^(.+) + if ( !$doc->doctype->publicId && !$doc->doctype->systemId ) { + $quirksmode = false; + } + + // not XHTML + if ( !preg_match("/xhtml/i", $doc->doctype->publicId) ) { + $quirksmode = true; + } + } + } + + $this->_xml = $doc; + $this->_quirksmode = $quirksmode; + + $this->_tree = new Frame_Tree($this->_xml); + + restore_error_handler(); + + $this->restore_locale(); + } + + static function remove_text_nodes(DOMNode $node) { + $children = array(); + 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 Frame_Tree}, loads any CSS and applies the styles to + * the {@link Frame_Tree} + */ + protected function _process_html() { + $this->_tree->build_tree(); + + $this->_css->load_css_file(Stylesheet::DEFAULT_STYLESHEET, Stylesheet::ORIG_UA); + + $acceptedmedia = Stylesheet::$ACCEPTED_GENERIC_MEDIA_TYPES; + $acceptedmedia[] = $this->get_option("default_media_type"); + + // + $base_nodes = $this->_xml->getElementsByTagName("base"); + if ( $base_nodes->length && ($href = $base_nodes->item(0)->getAttribute("href")) ) { + list($this->_protocol, $this->_base_host, $this->_base_path) = explode_url($href); + } + + // 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->_base_host); + $this->_css->set_base_path($this->_base_path); + + // Get all the stylesheets so that they are processed in document order + $xpath = new DOMXPath($this->_xml); + $stylesheets = $xpath->query("//*[name() = 'link' or name() = 'style']"); + + foreach($stylesheets as $tag) { + switch (strtolower($tag->nodeName)) { + // load 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. + continue; + } + } + + $url = $tag->getAttribute("href"); + $url = build_url($this->_protocol, $this->_base_host, $this->_base_path, $url); + + $this->_css->load_css_file($url, Stylesheet::ORIG_AUTHOR); + } + break; + + // load + $child = $child->nextSibling; + } + } + else { + $css = $tag->nodeValue; + } + + $this->_css->load_css($css); + break; + } + } + } + + /** + * Sets the paper size & orientation + * + * @param string $size 'letter', 'legal', 'A4', etc. {@link CPDF_Adapter::$PAPER_SIZES} + * @param string $orientation 'portrait' or 'landscape' + */ + function set_paper($size, $orientation = "portrait") { + $this->_paper_size = $size; + $this->_paper_orientation = $orientation; + } + + /** + * Enable experimental caching capability + * @access private + */ + function enable_caching($cache_id) { + $this->_cache_id = $cache_id; + } + + /** + * Sets callbacks for events like rendering of pages and elements. + * The callbacks array contains arrays with 'event' set to 'begin_page', + * 'end_page', 'begin_frame', or 'end_frame' and 'f' set to a function or + * object plus method to be called. + * + * The function 'f' must take an array as argument, which contains info + * about the event. + * + * @param array $callbacks the set of callbacks to set + */ + function set_callbacks($callbacks) { + if (is_array($callbacks)) { + $this->_callbacks = array(); + foreach ($callbacks as $c) { + if (is_array($c) && isset($c['event']) && isset($c['f'])) { + $event = $c['event']; + $f = $c['f']; + if (is_callable($f) && is_string($event)) { + $this->_callbacks[$event][] = $f; + } + } + } + } + } + + /** + * Get the quirks mode + * + * @return boolean true if quirks mode is active + */ + function get_quirksmode(){ + return $this->_quirksmode; + } + + function parse_default_view($value) { + $valid = array("XYZ", "Fit", "FitH", "FitV", "FitR", "FitB", "FitBH", "FitBV"); + + $options = preg_split("/\s*,\s*/", trim($value)); + $default_view = array_shift($options); + + if ( !in_array($default_view, $valid) ) { + return false; + } + + $this->set_default_view($default_view, $options); + return true; + } + + /** + * Renders the HTML to PDF + */ + function render() { + $this->save_locale(); + + $log_output_file = $this->get_option("log_output_file"); + if ( $log_output_file ) { + if ( !file_exists($log_output_file) && is_writable(dirname($log_output_file)) ) { + touch($log_output_file); + } + + $this->_start_time = microtime(true); + ob_start(); + } + + //enable_mem_profile(); + + $this->_process_html(); + + $this->_css->apply_styles($this->_tree); + + // @page style rules : size, margins + $page_styles = $this->_css->get_page_styles(); + + $base_page_style = $page_styles["base"]; + unset($page_styles["base"]); + + foreach($page_styles as $_page_style) { + $_page_style->inherit($base_page_style); + } + + if ( is_array($base_page_style->size) ) { + $this->set_paper(array(0, 0, $base_page_style->size[0], $base_page_style->size[1])); + } + + $this->_pdf = Canvas_Factory::get_instance($this, $this->_paper_size, $this->_paper_orientation); + Font_Metrics::init($this->_pdf); + + if ( $this->get_option("enable_font_subsetting") && $this->_pdf instanceof CPDF_Adapter ) { + foreach ($this->_tree->get_frames() as $frame) { + $style = $frame->get_style(); + $node = $frame->get_node(); + + // Handle text nodes + if ( $node->nodeName === "#text" ) { + $this->_pdf->register_string_subset($style->font_family, $node->nodeValue); + continue; + } + + // Handle generated content (list items) + if ( $style->display === "list-item" ) { + $chars = List_Bullet_Renderer::get_counter_chars($style->list_style_type); + $this->_pdf->register_string_subset($style->font_family, $chars); + continue; + } + + // Handle other generated content (pseudo elements) + // FIXME: This only captures the text of the stylesheet declaration, + // not the actual generated content, and forces all possible counter + // values. See notes in issue #750. + if ( $frame->get_node()->nodeName == "dompdf_generated" ) { + // all possible counter values + $chars = List_Bullet_Renderer::get_counter_chars('decimal'); + $this->_pdf->register_string_subset($style->font_family, $chars); + $chars = List_Bullet_Renderer::get_counter_chars('upper-alpha'); + $this->_pdf->register_string_subset($style->font_family, $chars); + $chars = List_Bullet_Renderer::get_counter_chars('lower-alpha'); + $this->_pdf->register_string_subset($style->font_family, $chars); + $chars = List_Bullet_Renderer::get_counter_chars('lower-greek'); + $this->_pdf->register_string_subset($style->font_family, $chars); + // the text of the stylesheet declaration + $this->_pdf->register_string_subset($style->font_family, $style->content); + continue; + } + } + } + + $root = null; + + foreach ($this->_tree->get_frames() as $frame) { + // Set up the root frame + if ( is_null($root) ) { + $root = Frame_Factory::decorate_root( $this->_tree->get_root(), $this ); + continue; + } + + // Create the appropriate decorators, reflowers & positioners. + Frame_Factory::decorate_frame($frame, $this, $root); + } + + // Add meta information + $title = $this->_xml->getElementsByTagName("title"); + if ( $title->length ) { + $this->_pdf->add_info("Title", trim($title->item(0)->nodeValue)); + } + + $metas = $this->_xml->getElementsByTagName("meta"); + $labels = array( + "author" => "Author", + "keywords" => "Keywords", + "description" => "Subject", + ); + foreach($metas as $meta) { + $name = mb_strtolower($meta->getAttribute("name")); + $value = trim($meta->getAttribute("content")); + + if ( isset($labels[$name]) ) { + $this->_pdf->add_info($labels[$name], $value); + continue; + } + + if ( $name === "dompdf.view" && $this->parse_default_view($value) ) { + $this->_pdf->set_default_view($this->_default_view, $this->_default_view_options); + } + } + + $root->set_containing_block(0, 0, $this->_pdf->get_width(), $this->_pdf->get_height()); + $root->set_renderer(new Renderer($this)); + + // This is where the magic happens: + $root->reflow(); + + // Clean up cached images + Image_Cache::clear(); + + global $_dompdf_warnings, $_dompdf_show_warnings; + if ( $_dompdf_show_warnings ) { + echo 'DOMPDF Warnings
';
+      foreach ($_dompdf_warnings as $msg) {
+        echo $msg . "\n";
+      }
+      echo $this->get_canvas()->get_cpdf()->messages;
+      echo '
'; + flush(); + } + + $this->restore_locale(); + } + + /** + * Add meta information to the PDF after rendering + */ + function add_info($label, $value) { + if ( !is_null($this->_pdf) ) { + $this->_pdf->add_info($label, $value); + } + } + + /** + * Writes the output buffer in the log file + * + * @return void + */ + private function write_log() { + $log_output_file = $this->get_option("log_output_file"); + if ( !$log_output_file || !is_writable($log_output_file) ) { + return; + } + + $frames = Frame::$ID_COUNTER; + $memory = DOMPDF_memory_usage() / 1024; + $time = (microtime(true) - $this->_start_time) * 1000; + + $out = sprintf( + "%6d". + "%10.2f KB". + "%10.2f ms". + " ". + ($this->_quirksmode ? " ON" : "OFF"). + "
", $frames, $memory, $time); + + $out .= ob_get_clean(); + + $log_output_file = $this->get_option("log_output_file"); + file_put_contents($log_output_file, $out); + } + + /** + * Streams the PDF to the client + * + * The file will open a download dialog by default. The options + * parameter controls the output. Accepted options are: + * + * 'Accept-Ranges' => 1 or 0 - if this is not set to 1, then this + * header is not included, off by default this header seems to + * have caused some problems despite the fact that it is supposed + * to solve them, so I am leaving it off by default. + * + * 'compress' = > 1 or 0 - apply content stream compression, this is + * on (1) by default + * + * 'Attachment' => 1 or 0 - if 1, force the browser to open a + * download dialog, on (1) by default + * + * @param string $filename the name of the streamed file + * @param array $options header options (see above) + */ + function stream($filename, $options = null) { + $this->save_locale(); + + $this->write_log(); + + if ( !is_null($this->_pdf) ) { + $this->_pdf->stream($filename, $options); + } + + $this->restore_locale(); + } + + /** + * Returns the PDF as a string + * + * The file will open a download dialog by default. 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 + */ + function output($options = null) { + $this->save_locale(); + + $this->write_log(); + + if ( is_null($this->_pdf) ) { + return null; + } + + $output = $this->_pdf->output( $options ); + + $this->restore_locale(); + + return $output; + } + + /** + * Returns the underlying HTML document as a string + * + * @return string + */ + function output_html() { + return $this->_xml->saveHTML(); + } +} diff --git a/library/vendor/dompdf/include/dompdf_exception.cls.php b/library/vendor/dompdf/include/dompdf_exception.cls.php new file mode 100644 index 000000000..ca47fa036 --- /dev/null +++ b/library/vendor/dompdf/include/dompdf_exception.cls.php @@ -0,0 +1,26 @@ + + * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + */ + +/** + * Standard exception thrown by DOMPDF classes + * + * @package dompdf + */ +class DOMPDF_Exception extends Exception { + + /** + * Class constructor + * + * @param string $message Error message + * @param int $code Error code + */ + function __construct($message = null, $code = 0) { + parent::__construct($message, $code); + } + +} diff --git a/library/vendor/dompdf/include/dompdf_image_exception.cls.php b/library/vendor/dompdf/include/dompdf_image_exception.cls.php new file mode 100644 index 000000000..8fdecec52 --- /dev/null +++ b/library/vendor/dompdf/include/dompdf_image_exception.cls.php @@ -0,0 +1,26 @@ + + * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + */ + +/** + * Image exception thrown by DOMPDF + * + * @package dompdf + */ +class DOMPDF_Image_Exception extends DOMPDF_Exception { + + /** + * Class constructor + * + * @param string $message Error message + * @param int $code Error code + */ + function __construct($message = null, $code = 0) { + parent::__construct($message, $code); + } + +} diff --git a/library/vendor/dompdf/include/fixed_positioner.cls.php b/library/vendor/dompdf/include/fixed_positioner.cls.php new file mode 100644 index 000000000..31a2a079c --- /dev/null +++ b/library/vendor/dompdf/include/fixed_positioner.cls.php @@ -0,0 +1,88 @@ + + * @author Fabien Ménager + * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + */ + +/** + * Positions fixely positioned frames + */ +class Fixed_Positioner extends Positioner { + + function __construct(Frame_Decorator $frame) { parent::__construct($frame); } + + function position() { + + $frame = $this->_frame; + $style = $frame->get_original_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 = $initialcb_style->length_in_pt($initialcb_style->margin_top, $initialcb["h"]); + $margin_right = $initialcb_style->length_in_pt($initialcb_style->margin_right, $initialcb["w"]); + $margin_bottom = $initialcb_style->length_in_pt($initialcb_style->margin_bottom, $initialcb["h"]); + $margin_left = $initialcb_style->length_in_pt($initialcb_style->margin_left, $initialcb["w"]); + + // The needed computed style of the element + $height = $style->length_in_pt($style->height, $initialcb["h"]); + $width = $style->length_in_pt($style->width, $initialcb["w"]); + + $top = $style->length_in_pt($style->top, $initialcb["h"]); + $right = $style->length_in_pt($style->right, $initialcb["w"]); + $bottom = $style->length_in_pt($style->bottom, $initialcb["h"]); + $left = $style->length_in_pt($style->left, $initialcb["w"]); + + $y = $margin_top; + if ( isset($top) ) { + $y = $top + $margin_top; + if ( $top === "auto" ) { + $y = $margin_top; + if ( isset($bottom) && $bottom !== "auto" ) { + $y = $initialcb["h"] - $bottom - $margin_bottom; + $margin_height = $this->_frame->get_margin_height(); + if ( $margin_height !== "auto" ) { + $y -= $margin_height; + } + else { + $y -= $height; + } + } + } + } + + $x = $margin_left; + if ( isset($left) ) { + $x = $left + $margin_left; + if ( $left === "auto" ) { + $x = $margin_left; + if ( isset($right) && $right !== "auto" ) { + $x = $initialcb["w"] - $right - $margin_right; + $margin_width = $this->_frame->get_margin_width(); + if ( $margin_width !== "auto" ) { + $x -= $margin_width; + } + else { + $x -= $width; + } + } + } + } + + $frame->set_position($x, $y); + + $children = $frame->get_children(); + foreach($children as $child) { + $child->set_position($x, $y); + } + } +} \ No newline at end of file diff --git a/library/vendor/dompdf/include/font_metrics.cls.php b/library/vendor/dompdf/include/font_metrics.cls.php new file mode 100644 index 000000000..ad20d9119 --- /dev/null +++ b/library/vendor/dompdf/include/font_metrics.cls.php @@ -0,0 +1,363 @@ + + * @author Helmut Tischer + * @author Fabien Ménager + * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + */ + +require_once DOMPDF_LIB_DIR . "/class.pdf.php"; + +/** + * Name of the font cache 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 Font_Metrics::save_font_families(). + * This is typically done only from command line with load_font.php on converting + * ttf fonts to ufm with php-font-lib. + * + * Declared here because PHP5 prevents constants from being declared with expressions + */ +define('__DOMPDF_FONT_CACHE_FILE', DOMPDF_FONT_DIR . "dompdf_font_family_cache.php"); + +/** + * 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 Font_Metrics { + + /** + * @see __DOMPDF_FONT_CACHE_FILE + */ + const CACHE_FILE = __DOMPDF_FONT_CACHE_FILE; + + /** + * Underlying {@link Canvas} object to perform text size calculations + * + * @var Canvas + */ + static protected $_pdf = null; + + /** + * Array of font family names to font files + * + * Usually cached by the {@link load_font.php} script + * + * @var array + */ + static protected $_font_lookup = array(); + + + /** + * Class initialization + * + */ + static function init(Canvas $canvas = null) { + if (!self::$_pdf) { + if (!$canvas) { + $canvas = Canvas_Factory::get_instance(new DOMPDF()); + } + + self::$_pdf = $canvas; + } + } + + /** + * Calculates text size, in points + * + * @param string $text the text to be sized + * @param string $font the desired font + * @param float $size the desired font size + * @param float $word_spacing + * @param float $char_spacing + * + * @internal param float $spacing word spacing, if any + * @return float + */ + static 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); + + // @todo Make sure this cache is efficient before enabling it + static $cache = array(); + + if ( $text === "" ) { + return 0; + } + + // Don't cache long strings + $use_cache = !isset($text[50]); // Faster than strlen + + $key = "$font/$size/$word_spacing/$char_spacing"; + + if ( $use_cache && isset($cache[$key][$text]) ) { + return $cache[$key]["$text"]; + } + + $width = self::$_pdf->get_text_width($text, $font, $size, $word_spacing, $char_spacing); + + if ( $use_cache ) { + $cache[$key][$text] = $width; + } + + return $width; + } + + /** + * Calculates font height + * + * @param string $font + * @param float $size + * @return float + */ + static function get_font_height($font, $size) { + return self::$_pdf->get_font_height($font, $size); + } + + /** + * 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 DOMPDF_DEFAULT_FONT}) is used. The font file returned + * is the absolute pathname to the font file on the system. + * + * @param string $family_raw + * @param string $subtype_raw + * + * @return string + */ + static function get_font($family_raw, $subtype_raw = "normal") { + static $cache = array(); + + if ( isset($cache[$family_raw][$subtype_raw]) ) { + return $cache[$family_raw][$subtype_raw]; + } + + /* 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($subtype_raw); + + if ( $family_raw ) { + $family = str_replace( array("'", '"'), "", strtolower($family_raw)); + + if ( isset(self::$_font_lookup[$family][$subtype]) ) { + return $cache[$family_raw][$subtype_raw] = self::$_font_lookup[$family][$subtype]; + } + + return null; + } + + $family = "serif"; + + if ( isset(self::$_font_lookup[$family][$subtype]) ) { + return $cache[$family_raw][$subtype_raw] = self::$_font_lookup[$family][$subtype]; + } + + if ( !isset(self::$_font_lookup[$family]) ) { + return null; + } + + $family = self::$_font_lookup[$family]; + + foreach ( $family as $sub => $font ) { + if (strpos($subtype, $sub) !== false) { + return $cache[$family_raw][$subtype_raw] = $font; + } + } + + if ($subtype !== "normal") { + foreach ( $family as $sub => $font ) { + if ($sub !== "normal") { + return $cache[$family_raw][$subtype_raw] = $font; + } + } + } + + $subtype = "normal"; + + if ( isset($family[$subtype]) ) { + return $cache[$family_raw][$subtype_raw] = $family[$subtype]; + } + + return null; + } + + static function get_family($family) { + $family = str_replace( array("'", '"'), "", mb_strtolower($family)); + + if ( isset(self::$_font_lookup[$family]) ) { + return self::$_font_lookup[$family]; + } + + return null; + } + + /** + * Saves the stored font family cache + * + * The name and location of the cache file are determined by {@link + * Font_Metrics::CACHE_FILE}. This file should be writable by the + * webserver process. + * + * @see Font_Metrics::load_font_families() + */ + static function save_font_families() { + // replace the path to the DOMPDF font directories with the corresponding constants (allows for more portability) + $cache_data = var_export(self::$_font_lookup, true); + $cache_data = str_replace('\''.DOMPDF_FONT_DIR , 'DOMPDF_FONT_DIR . \'' , $cache_data); + $cache_data = str_replace('\''.DOMPDF_DIR , 'DOMPDF_DIR . \'' , $cache_data); + $cache_data = "<"."?php return $cache_data ?".">"; + file_put_contents(self::CACHE_FILE, $cache_data); + } + + /** + * Loads the stored font family cache + * + * @see save_font_families() + */ + static function load_font_families() { + $dist_fonts = require_once DOMPDF_DIR . "/lib/fonts/dompdf_font_family_cache.dist.php"; + + // FIXME: temporary step for font cache created before the font cache fix + if ( is_readable( DOMPDF_FONT_DIR . "dompdf_font_family_cache" ) ) { + $old_fonts = require_once DOMPDF_FONT_DIR . "dompdf_font_family_cache"; + // If the font family cache is still in the old format + if ( $old_fonts === 1 ) { + $cache_data = file_get_contents(DOMPDF_FONT_DIR . "dompdf_font_family_cache"); + file_put_contents(DOMPDF_FONT_DIR . "dompdf_font_family_cache", "<"."?php return $cache_data ?".">"); + $old_fonts = require_once DOMPDF_FONT_DIR . "dompdf_font_family_cache"; + } + $dist_fonts += $old_fonts; + } + + if ( !is_readable(self::CACHE_FILE) ) { + self::$_font_lookup = $dist_fonts; + return; + } + + self::$_font_lookup = require_once self::CACHE_FILE; + + // If the font family cache is still in the old format + if ( self::$_font_lookup === 1 ) { + $cache_data = file_get_contents(self::CACHE_FILE); + file_put_contents(self::CACHE_FILE, "<"."?php return $cache_data ?".">"); + self::$_font_lookup = require_once self::CACHE_FILE; + } + + // Merge provided fonts + self::$_font_lookup += $dist_fonts; + } + + static function get_type($type) { + if (preg_match("/bold/i", $type)) { + if (preg_match("/italic|oblique/i", $type)) { + $type = "bold_italic"; + } + else { + $type = "bold"; + } + } + elseif (preg_match("/italic|oblique/i", $type)) { + $type = "italic"; + } + else { + $type = "normal"; + } + + return $type; + } + + static function install_fonts($files) { + $names = array(); + + foreach($files as $file) { + $font = Font::load($file); + $records = $font->getData("name", "records"); + $type = self::get_type($records[2]); + $names[mb_strtolower($records[1])][$type] = $file; + } + + return $names; + } + + static function get_system_fonts() { + $files = glob("/usr/share/fonts/truetype/*.ttf") + + glob("/usr/share/fonts/truetype/*/*.ttf") + + glob("/usr/share/fonts/truetype/*/*/*.ttf") + + glob("C:\\Windows\\fonts\\*.ttf") + + glob("C:\\WinNT\\fonts\\*.ttf") + + glob("/mnt/c_drive/WINDOWS/Fonts/"); + + return self::install_fonts($files); + } + + /** + * Returns the current font lookup table + * + * @return array + */ + static function get_font_families() { + return self::$_font_lookup; + } + + static function set_font_family($fontname, $entry) { + self::$_font_lookup[mb_strtolower($fontname)] = $entry; + } + + static function register_font($style, $remote_file) { + $fontname = mb_strtolower($style["family"]); + $families = Font_Metrics::get_font_families(); + + $entry = array(); + if ( isset($families[$fontname]) ) { + $entry = $families[$fontname]; + } + + $local_file = DOMPDF_FONT_DIR . md5($remote_file); + $cache_entry = $local_file; + $local_file .= ".ttf"; + + $style_string = Font_Metrics::get_type("{$style['weight']} {$style['style']}"); + + if ( !isset($entry[$style_string]) ) { + $entry[$style_string] = $cache_entry; + + Font_Metrics::set_font_family($fontname, $entry); + + // Download the remote file + if ( !is_file($local_file) ) { + file_put_contents($local_file, file_get_contents($remote_file)); + } + + $font = Font::load($local_file); + + if (!$font) { + return false; + } + + $font->parse(); + $font->saveAdobeFontMetrics("$cache_entry.ufm"); + + // Save the changes + Font_Metrics::save_font_families(); + } + + return true; + } +} + +Font_Metrics::load_font_families(); diff --git a/library/vendor/dompdf/include/frame.cls.php b/library/vendor/dompdf/include/frame.cls.php new file mode 100644 index 000000000..bc2f26d40 --- /dev/null +++ b/library/vendor/dompdf/include/frame.cls.php @@ -0,0 +1,1191 @@ + + * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + */ + +/** + * 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. + * + * @access protected + * @package dompdf + */ +class Frame { + + /** + * 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 string + */ + protected $_id; + + /** + * Unique id counter + */ + static /*protected*/ $ID_COUNTER = 0; + + /** + * This frame's calculated style + * + * @var Style + */ + protected $_style; + + /** + * This frame's original style. Needed for cases where frames are + * split across pages. + * + * @var Style + */ + protected $_original_style; + + /** + * This frame's parent in the document tree. + * + * @var Frame + */ + protected $_parent; + + /** + * This frame's children + * + * @var Frame[] + */ + protected $_frame_list; + + /** + * 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 Frame_Decorator + */ + protected $_decorator; + + /** + * This frame's containing line box + * + * @var Line_Box + */ + protected $_containing_line; + + protected $_is_cache = array(); + + /** + * Tells wether the frame was already pushed to the next page + * + * @var bool + */ + public $_already_pushed = false; + + public $_float_next_line = false; + + /** + * Tells wether the frame was split + * + * @var bool + */ + public $_splitted; + + static $_ws_state = self::WS_SPACE; + + const WS_TEXT = 1; + const WS_SPACE = 2; + + /** + * Class destructor + */ + function __destruct() { + clear_object($this); + } + + /** + * Class constructor + * + * @param DOMNode $node the DOMNode this frame represents + */ + 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->_original_style = null; + + $this->_containing_block = array( + "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 = array( + "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; + } + + switch(self::$_ws_state) { + case self::WS_SPACE: + $node = $this->_node; + + if ( $node->nodeName === "#text" ) { + $node->nodeValue = preg_replace("/[ \t\r\n\f]+/u", " ", $node->nodeValue); + + // starts with a whitespace + if ( isset($node->nodeValue[0]) && $node->nodeValue[0] === " " ) { + $node->nodeValue = ltrim($node->nodeValue); + } + + // if not empty + if ( $node->nodeValue !== "" ) { + // change the current state (text) + self::$_ws_state = self::WS_TEXT; + + // ends with a whitespace + if ( preg_match("/[ \t\r\n\f]+$/u", $node->nodeValue) ) { + $node->nodeValue = ltrim($node->nodeValue); + } + } + } + break; + + case self::WS_TEXT: + } + } + + protected function ws_keep(){ + $whitespace = $this->get_style()->white_space; + return in_array($whitespace, array("pre", "pre-wrap", "pre-line")); + } + + 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 + */ + 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->dispose(); + $this->_style = null; + unset($this->_style); + + $this->_original_style->dispose(); + $this->_original_style = null; + unset($this->_original_style); + + } + + // Re-initialize the frame + 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 = null; + unset($this->_style); + $this->_style = clone $this->_original_style; + } + + //........................................................................ + + /** + * @return DOMElement|DOMText + */ + function get_node() { + return $this->_node; + } + + /** + * @return string + */ + function get_id() { + return $this->_id; + } + + /** + * @return Style + */ + function get_style() { + return $this->_style; + } + + /** + * @return Style + */ + function get_original_style() { + return $this->_original_style; + } + + /** + * @return Frame + */ + function get_parent() { + return $this->_parent; + } + + /** + * @return Frame_Decorator + */ + function get_decorator() { + return $this->_decorator; + } + + /** + * @return Frame + */ + function get_first_child() { + return $this->_first_child; + } + + /** + * @return Frame + */ + function get_last_child() { + return $this->_last_child; + } + + /** + * @return Frame + */ + function get_prev_sibling() { + return $this->_prev_sibling; + } + + /** + * @return Frame + */ + function get_next_sibling() { + return $this->_next_sibling; + } + + /** + * @return FrameList|Frame[] + */ + function get_children() { + if ( isset($this->_frame_list) ) { + return $this->_frame_list; + } + + $this->_frame_list = new FrameList($this); + return $this->_frame_list; + } + + // Layout property accessors + + /** + * Containing block dimensions + * + * @param $i string The key of the wanted containing block's dimension (x, y, x, h) + * + * @return float[]|float + */ + function get_containing_block($i = null) { + if ( isset($i) ) { + return $this->_containing_block[$i]; + } + return $this->_containing_block; + } + + /** + * Block position + * + * @param $i string The key of the wanted position value (x, y) + * + * @return array|float + */ + function get_position($i = null) { + if ( isset($i) ) { + return $this->_position[$i]; + } + return $this->_position; + } + + //........................................................................ + + /** + * Return the height of the margin box of the frame, in pt. Meaningless + * unless the height has been calculated properly. + * + * @return float + */ + function get_margin_height() { + $style = $this->_style; + + return $style->length_in_pt(array( + $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"]); + } + + /** + * Return the width of the margin box of the frame, in pt. Meaningless + * unless the width has been calculated properly. + * + * @return float + */ + function get_margin_width() { + $style = $this->_style; + + return $style->length_in_pt(array( + $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"]); + } + + function get_break_margins(){ + $style = $this->_style; + + return $style->length_in_pt(array( + //$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"]); + } + + /** + * Return the padding box (x,y,w,h) of the frame + * + * @return array + */ + function get_padding_box() { + $style = $this->_style; + $cb = $this->_containing_block; + + $x = $this->_position["x"] + + $style->length_in_pt(array($style->margin_left, + $style->border_left_width), + $cb["w"]); + + $y = $this->_position["y"] + + $style->length_in_pt(array($style->margin_top, + $style->border_top_width), + $cb["h"]); + + $w = $style->length_in_pt(array($style->padding_left, + $style->width, + $style->padding_right), + $cb["w"]); + + $h = $style->length_in_pt(array($style->padding_top, + $style->height, + $style->padding_bottom), + $cb["h"]); + + return array(0 => $x, "x" => $x, + 1 => $y, "y" => $y, + 2 => $w, "w" => $w, + 3 => $h, "h" => $h); + } + + /** + * Return the border box of the frame + * + * @return array + */ + function get_border_box() { + $style = $this->_style; + $cb = $this->_containing_block; + + $x = $this->_position["x"] + $style->length_in_pt($style->margin_left, $cb["w"]); + + $y = $this->_position["y"] + $style->length_in_pt($style->margin_top, $cb["h"]); + + $w = $style->length_in_pt(array($style->border_left_width, + $style->padding_left, + $style->width, + $style->padding_right, + $style->border_right_width), + $cb["w"]); + + $h = $style->length_in_pt(array($style->border_top_width, + $style->padding_top, + $style->height, + $style->padding_bottom, + $style->border_bottom_width), + $cb["h"]); + + return array(0 => $x, "x" => $x, + 1 => $y, "y" => $y, + 2 => $w, "w" => $w, + 3 => $h, "h" => $h); + } + + function get_opacity($opacity = null) { + if ( $opacity !== null ) { + $this->set_opacity($opacity); + } + return $this->_opacity; + } + + /** + * @return Line_Box + */ + function &get_containing_line() { + return $this->_containing_line; + } + + //........................................................................ + + // Set methods + 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); + } + } + + function set_style(Style $style) { + if ( is_null($this->_style) ) { + $this->_original_style = clone $style; + } + + //$style->set_frame($this); + $this->_style = $style; + } + + function set_decorator(Frame_Decorator $decorator) { + $this->_decorator = $decorator; + } + + 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; + } + } + + function set_position($x = null, $y = null) { + if ( is_array($x) ) { + list($x, $y) = array($x["x"], $x["y"]); + } + + if ( is_numeric($x) ) { + $this->_position["x"] = $x; + } + + if ( is_numeric($y) ) { + $this->_position["y"] = $y; + } + } + + function set_opacity($opacity) { + $parent = $this->get_parent(); + $base_opacity = (($parent && $parent->_opacity !== null) ? $parent->_opacity : 1.0); + $this->_opacity = $base_opacity * $opacity; + } + + function set_containing_line(Line_Box $line) { + $this->_containing_line = $line; + } + + //........................................................................ + + /** + * Tells if the frame is a text node + * @return bool + */ + function is_text_node() { + if ( isset($this->_is_cache["text_node"]) ) { + return $this->_is_cache["text_node"]; + } + + return $this->_is_cache["text_node"] = ($this->get_node()->nodeName === "#text"); + } + + function is_positionned() { + if ( isset($this->_is_cache["positionned"]) ) { + return $this->_is_cache["positionned"]; + } + + $position = $this->get_style()->position; + + return $this->_is_cache["positionned"] = in_array($position, Style::$POSITIONNED_TYPES); + } + + function is_absolute() { + if ( isset($this->_is_cache["absolute"]) ) { + return $this->_is_cache["absolute"]; + } + + $position = $this->get_style()->position; + + return $this->_is_cache["absolute"] = ($position === "absolute" || $position === "fixed"); + } + + function is_block() { + 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); + } + + function is_in_flow() { + if ( isset($this->_is_cache["in_flow"]) ) { + return $this->_is_cache["in_flow"]; + } + + $enable_css_float = $this->get_style()->get_stylesheet()->get_dompdf()->get_option("enable_css_float"); + return $this->_is_cache["in_flow"] = !($enable_css_float && $this->get_style()->float !== "none" || $this->is_absolute()); + } + + function is_pre(){ + 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, array("pre", "pre-wrap")); + } + + function is_table(){ + 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); + } + + + /** + * Inserts a new child at the beginning of the Frame + * + * @param $child Frame The new Frame to insert + * @param $update_node boolean Whether or not to update the DOM + */ + 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 $child Frame The new Frame to insert + * @param $update_node boolean Whether or not to update the DOM + */ + 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; + $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 $new_child Frame The new Frame to insert + * @param $ref Frame The Frame after the new Frame + * @param $update_node boolean Whether or not to update the DOM + * + * @throws DOMPDF_Exception + */ + 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 DOMPDF_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 $new_child Frame The new Frame to insert + * @param $ref Frame The Frame before the new Frame + * @param $update_node boolean Whether or not to update the DOM + * + * @throws DOMPDF_Exception + */ + 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 DOMPDF_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 boolean $update_node Whether or not to remove the DOM node + * + * @throws DOMPDF_Exception + * @return Frame The removed child frame + */ + function remove_child(Frame $child, $update_node = true) { + if ( $child->_parent !== $this ) { + throw new DOMPDF_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: + function __toString() { + // Skip empty text frames +// if ( $this->is_text_node() && +// preg_replace("/\s/", "", $this->_node->data) === "" ) +// return ""; + + + $str = "" . $this->_node->nodeName . ":
"; + //$str .= spl_object_hash($this->_node) . "
"; + $str .= "Id: " .$this->get_id() . "
"; + $str .= "Class: " .get_class($this) . "
"; + + if ( $this->is_text_node() ) { + $tmp = htmlspecialchars($this->_node->nodeValue); + $str .= "
'" .  mb_substr($tmp,0,70) .
+        (mb_strlen($tmp) > 70 ? "..." : "") . "'
"; + } + elseif ( $css_class = $this->_node->getAttribute("class") ) { + $str .= "CSS class: '$css_class'
"; + } + + if ( $this->_parent ) { + $str .= "\nParent:" . $this->_parent->_node->nodeName . + " (" . spl_object_hash($this->_parent->_node) . ") " . + "
"; + } + + if ( $this->_prev_sibling ) { + $str .= "Prev: " . $this->_prev_sibling->_node->nodeName . + " (" . spl_object_hash($this->_prev_sibling->_node) . ") " . + "
"; + } + + if ( $this->_next_sibling ) { + $str .= "Next: " . $this->_next_sibling->_node->nodeName . + " (" . spl_object_hash($this->_next_sibling->_node) . ") " . + "
"; + } + + $d = $this->get_decorator(); + while ($d && $d != $d->get_decorator()) { + $str .= "Decorator: " . get_class($d) . "
"; + $d = $d->get_decorator(); + } + + $str .= "Position: " . pre_r($this->_position, true); + $str .= "\nContaining block: " . pre_r($this->_containing_block, true); + $str .= "\nMargin width: " . pre_r($this->get_margin_width(), true); + $str .= "\nMargin height: " . pre_r($this->get_margin_height(), true); + + $str .= "\nStyle:
". $this->_style->__toString() . "
"; + + if ( $this->_decorator instanceof Block_Frame_Decorator ) { + $str .= "Lines:
";
+      foreach ($this->_decorator->get_line_boxes() as $line) {
+        foreach ($line->get_frames() as $frame) {
+          if ($frame instanceof Text_Frame_Decorator) {
+            $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 .= "
"; + } + + $str .= "\n"; + if ( php_sapi_name() === "cli" ) { + $str = strip_tags(str_replace(array("
","",""), + array("\n","",""), + $str)); + } + + return $str; + } +} + +//------------------------------------------------------------------------ + +/** + * Linked-list IteratorAggregate + * + * @access private + * @package dompdf + */ +class FrameList implements IteratorAggregate { + protected $_frame; + + function __construct($frame) { $this->_frame = $frame; } + function getIterator() { return new FrameListIterator($this->_frame); } +} + +/** + * Linked-list Iterator + * + * Returns children in order and allows for list to change during iteration, + * provided the changes occur to or after the current element + * + * @access private + * @package dompdf + */ +class FrameListIterator implements Iterator { + + /** + * @var Frame + */ + protected $_parent; + + /** + * @var Frame + */ + protected $_cur; + + /** + * @var int + */ + protected $_num; + + function __construct(Frame $frame) { + $this->_parent = $frame; + $this->_cur = $frame->get_first_child(); + $this->_num = 0; + } + + function rewind() { + $this->_cur = $this->_parent->get_first_child(); + $this->_num = 0; + } + + /** + * @return bool + */ + function valid() { + return isset($this->_cur);// && ($this->_cur->get_prev_sibling() === $this->_prev); + } + + function key() { return $this->_num; } + + /** + * @return Frame + */ + function current() { return $this->_cur; } + + /** + * @return Frame + */ + function next() { + + $ret = $this->_cur; + if ( !$ret ) { + return null; + } + + $this->_cur = $this->_cur->get_next_sibling(); + $this->_num++; + return $ret; + } +} + +//------------------------------------------------------------------------ + +/** + * Pre-order IteratorAggregate + * + * @access private + * @package dompdf + */ +class FrameTreeList implements IteratorAggregate { + /** + * @var Frame + */ + protected $_root; + + function __construct(Frame $root) { $this->_root = $root; } + + /** + * @return FrameTreeIterator + */ + function getIterator() { return new FrameTreeIterator($this->_root); } +} + +/** + * Pre-order Iterator + * + * Returns frames in preorder traversal order (parent then children) + * + * @access private + * @package dompdf + */ +class FrameTreeIterator implements Iterator { + /** + * @var Frame + */ + protected $_root; + protected $_stack = array(); + + /** + * @var int + */ + protected $_num; + + function __construct(Frame $root) { + $this->_stack[] = $this->_root = $root; + $this->_num = 0; + } + + function rewind() { + $this->_stack = array($this->_root); + $this->_num = 0; + } + + /** + * @return bool + */ + function valid() { + return count($this->_stack) > 0; + } + + /** + * @return int + */ + function key() { + return $this->_num; + } + + /** + * @return Frame + */ + function current() { + return end($this->_stack); + } + + /** + * @return Frame + */ + function next() { + $b = end($this->_stack); + + // Pop last element + unset($this->_stack[ key($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; + } + } + + return $b; + } +} + diff --git a/library/vendor/dompdf/include/frame_decorator.cls.php b/library/vendor/dompdf/include/frame_decorator.cls.php new file mode 100644 index 000000000..987e32ee1 --- /dev/null +++ b/library/vendor/dompdf/include/frame_decorator.cls.php @@ -0,0 +1,717 @@ + + * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + */ + +/** + * Base Frame_Decorator class + * + * @access private + * @package dompdf + */ +abstract class Frame_Decorator extends Frame { + const DEFAULT_COUNTER = "-dompdf-default-counter"; + + public $_counters = array(); // array([id] => counter_value) (for generated content) + + /** + * The root node of the DOM tree + * + * @var Frame + */ + protected $_root; + + /** + * The decorated frame + * + * @var Frame + */ + protected $_frame; + + /** + * Positioner object used to position this frame (Strategy pattern) + * + * @var Positioner + */ + protected $_positioner; + + /** + * Reflower object used to calculate frame dimensions (Strategy pattern) + * + * @var Frame_Reflower + */ + protected $_reflower; + + /** + * Reference to the current dompdf instance + * + * @var DOMPDF + */ + protected $_dompdf; + + /** + * First block parent + * + * @var Block_Frame_Decorator + */ + private $_block_parent; + + /** + * First positionned parent (position: relative | absolute | fixed) + * + * @var Frame_Decorator + */ + private $_positionned_parent; + + /** + * 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": foribly 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 Frame + */ + function copy(DOMNode $node) { + $frame = new Frame($node); + $frame->set_style(clone $this->_frame->get_original_style()); + + return Frame_Factory::decorate_frame($frame, $this->_dompdf, $this->_root); + } + + /** + * Create a deep copy: copy this node and all children + * + * @return Frame + */ + function deep_copy() { + $frame = new Frame($this->get_node()->cloneNode()); + $frame->set_style(clone $this->_frame->get_original_style()); + + $deco = Frame_Factory::decorate_frame($frame, $this->_dompdf, $this->_root); + + foreach ($this->get_children() as $child) { + $deco->append_child($child->deep_copy()); + } + + return $deco; + } + + /** + * Delegate calls to decorated frame object + */ + function reset() { + $this->_frame->reset(); + + $this->_counters = array(); + + // Reset all children + foreach ($this->get_children() as $child) { + $child->reset(); + } + } + + // Getters ----------- + function get_id() { + return $this->_frame->get_id(); + } + + /** + * @return Frame + */ + function get_frame() { + return $this->_frame; + } + + /** + * @return DOMElement|DOMText + */ + function get_node() { + return $this->_frame->get_node(); + } + + /** + * @return Style + */ + function get_style() { + return $this->_frame->get_style(); + } + + /** + * @return Style + */ + function get_original_style() { + return $this->_frame->get_original_style(); + } + + /** + * @param integer $i + * + * @return array|float + */ + function get_containing_block($i = null) { + return $this->_frame->get_containing_block($i); + } + + /** + * @param integer $i + * + * @return array|float + */ + function get_position($i = null) { + return $this->_frame->get_position($i); + } + + /** + * @return DOMPDF + */ + function get_dompdf() { + return $this->_dompdf; + } + + /** + * @return float + */ + function get_margin_height() { + return $this->_frame->get_margin_height(); + } + + /** + * @return float + */ + function get_margin_width() { + return $this->_frame->get_margin_width(); + } + + /** + * @return array + */ + function get_padding_box() { + return $this->_frame->get_padding_box(); + } + + /** + * @return array + */ + function get_border_box() { + return $this->_frame->get_border_box(); + } + + /** + * @param integer $id + */ + function set_id($id) { + $this->_frame->set_id($id); + } + + /** + * @param Style $style + */ + function set_style(Style $style) { + $this->_frame->set_style($style); + } + + /** + * @param float $x + * @param float $y + * @param float $w + * @param float $h + */ + function set_containing_block($x = null, $y = null, $w = null, $h = null) { + $this->_frame->set_containing_block($x, $y, $w, $h); + } + + /** + * @param float $x + * @param float $y + */ + function set_position($x = null, $y = null) { + $this->_frame->set_position($x, $y); + } + + /** + * @return string + */ + function __toString() { + return $this->_frame->__toString(); + } + + /** + * @param Frame $child + * @param bool $update_node + */ + function prepend_child(Frame $child, $update_node = true) { + while ( $child instanceof Frame_Decorator ) { + $child = $child->_frame; + } + + $this->_frame->prepend_child($child, $update_node); + } + + /** + * @param Frame $child + * @param bool $update_node + */ + function append_child(Frame $child, $update_node = true) { + while ( $child instanceof Frame_Decorator ) { + $child = $child->_frame; + } + + $this->_frame->append_child($child, $update_node); + } + + /** + * @param Frame $new_child + * @param Frame $ref + * @param bool $update_node + */ + function insert_child_before(Frame $new_child, Frame $ref, $update_node = true) { + while ( $new_child instanceof Frame_Decorator ) { + $new_child = $new_child->_frame; + } + + if ( $ref instanceof Frame_Decorator ) { + $ref = $ref->_frame; + } + + $this->_frame->insert_child_before($new_child, $ref, $update_node); + } + + /** + * @param Frame $new_child + * @param Frame $ref + * @param bool $update_node + */ + function insert_child_after(Frame $new_child, Frame $ref, $update_node = true) { + while ( $new_child instanceof Frame_Decorator ) { + $new_child = $new_child->_frame; + } + + while ( $ref instanceof Frame_Decorator ) { + $ref = $ref->_frame; + } + + $this->_frame->insert_child_after($new_child, $ref, $update_node); + } + + /** + * @param Frame $child + * @param bool $update_node + * + * @return Frame + */ + function remove_child(Frame $child, $update_node = true) { + while ( $child instanceof Frame_Decorator ) { + $child = $child->_frame; + } + + return $this->_frame->remove_child($child, $update_node); + } + + /** + * @return Frame_Decorator + */ + function get_parent() { + $p = $this->_frame->get_parent(); + if ( $p && $deco = $p->get_decorator() ) { + while ( $tmp = $deco->get_decorator() ) { + $deco = $tmp; + } + + return $deco; + } + else if ( $p ) { + return $p; + } + + return null; + } + + /** + * @return Frame_Decorator + */ + 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 Frame_Decorator + */ + 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 Frame_Decorator + */ + 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 Frame_Decorator + */ + 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 FrameTreeList + */ + function get_subtree() { + return new FrameTreeList($this); + } + + function set_positioner(Positioner $posn) { + $this->_positioner = $posn; + if ( $this->_frame instanceof Frame_Decorator ) { + $this->_frame->set_positioner($posn); + } + } + + function set_reflower(Frame_Reflower $reflower) { + $this->_reflower = $reflower; + if ( $this->_frame instanceof Frame_Decorator ) { + $this->_frame->set_reflower( $reflower ); + } + } + + /** + * @return Frame_Reflower + */ + function get_reflower() { + return $this->_reflower; + } + + /** + * @param Frame $root + */ + function set_root(Frame $root) { + $this->_root = $root; + + if ( $this->_frame instanceof Frame_Decorator ) { + $this->_frame->set_root($root); + } + } + + /** + * @return Page_Frame_Decorator + */ + function get_root() { + return $this->_root; + } + + /** + * @return Block_Frame_Decorator + */ + function find_block_parent() { + // Find our nearest block level parent + $p = $this->get_parent(); + + while ( $p ) { + if ( $p->is_block() ) { + break; + } + + $p = $p->get_parent(); + } + + return $this->_block_parent = $p; + } + + /** + * @return Frame_Decorator + */ + function find_positionned_parent() { + // Find our nearest relative positionned parent + $p = $this->get_parent(); + while ( $p ) { + if ( $p->is_positionned() ) { + break; + } + + $p = $p->get_parent(); + } + + if ( !$p ) { + $p = $this->_root->get_first_child(); // + } + + return $this->_positionned_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 $child + * @param boolean $force_pagebreak + * + * @throws DOMPDF_Exception + * @return void + */ + function split(Frame $child = null, $force_pagebreak = false) { + // decrement any counters that were incremented on the current node, unless that node is the body + $style = $this->_frame->get_style(); + if ( $this->_frame->get_node()->nodeName !== "body" && $style->counter_increment && ($decrement = $style->counter_increment) !== "none" ) { + $this->decrement_counters($decrement); + } + + if ( is_null( $child ) ) { + // check for counter increment on :before content (always a child of the selected element @link Frame_Reflower::_set_content) + // this can push the current node to the next page before counter rules have bubbled up (but only if + // it's been rendered, thus the position check) + if ( !$this->is_text_node() && $this->get_node()->hasAttribute("dompdf_before_frame_id") ) { + foreach($this->_frame->get_children() as $child) { + if ( $this->get_node()->getAttribute("dompdf_before_frame_id") == $child->get_id() && $child->get_position('x') !== NULL ) { + $style = $child->get_style(); + if ( $style->counter_increment && ($decrement = $style->counter_increment) !== "none" ) { + $this->decrement_counters($decrement); + } + } + } + } + $this->get_parent()->split($this, $force_pagebreak); + return; + } + + if ( $child->get_parent() !== $this ) { + throw new DOMPDF_Exception("Unable to split: frame is not a child of this one."); + } + + $node = $this->_frame->get_node(); + + $split = $this->copy( $node->cloneNode() ); + $split->reset(); + $split->get_original_style()->text_indent = 0; + $split->_splitted = true; + + // The body's properties must be kept + if ( $node->nodeName !== "body" ) { + // Style reset on the first and second parts + $style = $this->_frame->get_style(); + $style->margin_bottom = 0; + $style->padding_bottom = 0; + $style->border_bottom = 0; + + // second + $orig_style = $split->get_original_style(); + $orig_style->text_indent = 0; + $orig_style->margin_top = 0; + $orig_style->padding_top = 0; + $orig_style->border_top = 0; + } + + $this->get_parent()->insert_child_after($split, $this); + + // Add $frame 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, $force_pagebreak); + + // If this node resets a counter save the current value to use when rendering on the next page + if ( $style->counter_reset && ( $reset = $style->counter_reset ) !== "none" ) { + $vars = preg_split( '/\s+/' , trim( $reset ) , 2 ); + $split->_counters[ '__' . $vars[0] ] = $this->lookup_counter_frame( $vars[0] )->_counters[$vars[0]]; + } + } + + function reset_counter($id = self::DEFAULT_COUNTER, $value = 0) { + $this->get_parent()->_counters[$id] = intval($value); + } + + function decrement_counters($counters) { + foreach($counters as $id => $increment) { + $this->increment_counter($id, intval($increment) * -1); + } + } + + function increment_counters($counters) { + foreach($counters as $id => $increment) { + $this->increment_counter($id, intval($increment)); + } + } + + function increment_counter($id = self::DEFAULT_COUNTER, $increment = 1) { + $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; + } + } + + 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; + } + } + + // TODO: What version is the best : this one or the one in List_Bullet_Renderer ? + function counter_value($id = self::DEFAULT_COUNTER, $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"); + + case "lower-roman": + return dec2roman($value); + + case "upper-roman": + return mb_strtoupper(dec2roman($value)); + + case "lower-latin": + case "lower-alpha": + return chr( ($value % 26) + ord('a') - 1); + + case "upper-latin": + case "upper-alpha": + return chr( ($value % 26) + ord('A') - 1); + + case "lower-greek": + return unichr($value + 944); + + case "upper-greek": + return unichr($value + 912); + } + } + + final function position() { + $this->_positioner->position(); + } + + final function move($offset_x, $offset_y, $ignore_self = false) { + $this->_positioner->move($offset_x, $offset_y, $ignore_self); + } + + final function reflow(Block_Frame_Decorator $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); + } + + final function get_min_max_width() { + return $this->_reflower->get_min_max_width(); + } +} diff --git a/library/vendor/dompdf/include/frame_factory.cls.php b/library/vendor/dompdf/include/frame_factory.cls.php new file mode 100644 index 000000000..70813d2e3 --- /dev/null +++ b/library/vendor/dompdf/include/frame_factory.cls.php @@ -0,0 +1,252 @@ + + * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + */ + +/** + * Contains frame decorating logic + * + * This class is responsible for assigning the correct {@link Frame_Decorator}, + * {@link Positioner}, and {@link Frame_Reflower} 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) + * + * @access private + * @package dompdf + */ +class Frame_Factory { + + /** + * Decorate the root Frame + * + * @param $root Frame The frame to decorate + * @param $dompdf DOMPDF The dompdf instance + * @return Page_Frame_Decorator + */ + static function decorate_root(Frame $root, DOMPDF $dompdf) { + $frame = new Page_Frame_Decorator($root, $dompdf); + $frame->set_reflower( new Page_Frame_Reflower($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 $root The frame to decorate + * + * @throws DOMPDF_Exception + * @return Frame_Decorator + * FIXME: this is admittedly a little smelly... + */ + static function decorate_frame(Frame $frame, DOMPDF $dompdf, Frame $root = null) { + if ( is_null($dompdf) ) { + throw new DOMPDF_Exception("The DOMPDF argument is required"); + } + + $style = $frame->get_style(); + + // Floating (and more generally out-of-flow) elements are blocks + // http://coding.smashingmagazine.com/2007/05/01/css-float-theory-things-you-should-know/ + if ( !$frame->is_in_flow() && in_array($style->display, Style::$INLINE_TYPES)) { + $style->display = "block"; + } + + $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 { + $enable_css_float = $dompdf->get_option("enable_css_float"); + if ( $enable_css_float && $style->float !== "none" ) { + $decorator = "Block"; + $reflower = "Block"; + } + 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 = "Null"; + $decorator = "Table_Row_Group"; + $reflower = "Table_Row_Group"; + break; + + case "table-row": + $positioner = "Null"; + $decorator = "Table_Row"; + $reflower = "Table_Row"; + break; + + case "table-cell": + $positioner = "Table_Cell"; + $decorator = "Table_Cell"; + $reflower = "Table_Cell"; + 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 = "List_Bullet"; + } + + if ( $style->list_style_image !== "none" ) { + $decorator = "List_Bullet_Image"; + } + else { + $decorator = "List_Bullet"; + } + + $reflower = "List_Bullet"; + break; + + case "-dompdf-image": + $positioner = "Inline"; + $decorator = "Image"; + $reflower = "Image"; + break; + + case "-dompdf-br": + $positioner = "Inline"; + $decorator = "Inline"; + $reflower = "Inline"; + break; + + default: + // FIXME: should throw some sort of warning or something? + case "none": + if ( $style->_dompdf_keep !== "yes" ) { + // Remove the node and the frame + $frame->get_parent()->remove_child($frame); + return; + } + + $positioner = "Null"; + $decorator = "Null"; + $reflower = "Null"; + break; + } + + // Handle CSS position + $position = $style->position; + + if ( $position === "absolute" ) { + $positioner = "Absolute"; + } + else if ( $position === "fixed" ) { + $positioner = "Fixed"; + } + + $node = $frame->get_node(); + + // Handle nodeName + if ( $node->nodeName === "img" ) { + $style->display = "-dompdf-image"; + $decorator = "Image"; + $reflower = "Image"; + } + + $positioner .= "_Positioner"; + $decorator .= "_Frame_Decorator"; + $reflower .= "_Frame_Reflower"; + + $deco = new $decorator($frame, $dompdf); + + $deco->set_positioner( new $positioner($deco) ); + $deco->set_reflower( new $reflower($deco) ); + + if ( $root ) { + $deco->set_root($root); + } + + if ( $display === "list-item" ) { + // Insert a list-bullet frame + $xml = $dompdf->get_dom(); + $bullet_node = $xml->createElement("bullet"); // arbitrary choice + $b_f = new Frame($bullet_node); + + $node = $frame->get_node(); + $parent_node = $node->parentNode; + + if ( $parent_node ) { + 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 = $parent_node->getAttribute("dompdf-counter")+1; + } + } + + $parent_node->setAttribute("dompdf-counter", $index); + $bullet_node->setAttribute("dompdf-counter", $index); + } + + $new_style = $dompdf->get_css()->create_style(); + $new_style->display = "-dompdf-list-bullet"; + $new_style->inherit($style); + $b_f->set_style($new_style); + + $deco->prepend_child( Frame_Factory::decorate_frame($b_f, $dompdf, $root) ); + } + + return $deco; + } +} diff --git a/library/vendor/dompdf/include/frame_reflower.cls.php b/library/vendor/dompdf/include/frame_reflower.cls.php new file mode 100644 index 000000000..576039e1f --- /dev/null +++ b/library/vendor/dompdf/include/frame_reflower.cls.php @@ -0,0 +1,458 @@ + + * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + */ + +/** + * 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. + * + * @access private + * @package dompdf + */ +abstract class Frame_Reflower { + + /** + * Frame for this reflower + * + * @var Frame + */ + protected $_frame; + + /** + * Cached min/max size + * + * @var array + */ + protected $_min_max_cache; + + function __construct(Frame $frame) { + $this->_frame = $frame; + $this->_min_max_cache = null; + } + + function dispose() { + clear_object($this); + } + + /** + * @return DOMPDF + */ + function get_dompdf() { + return $this->_frame->get_dompdf(); + } + + /** + * Collapse frames margins + * http://www.w3.org/TR/CSS2/box.html#collapsing-margins + */ + protected function _collapse_margins() { + $frame = $this->_frame; + $cb = $frame->get_containing_block(); + $style = $frame->get_style(); + + if ( !$frame->is_in_flow() ) { + return; + } + + $t = $style->length_in_pt($style->margin_top, $cb["h"]); + $b = $style->length_in_pt($style->margin_bottom, $cb["h"]); + + // Handle 'auto' values + if ( $t === "auto" ) { + $style->margin_top = "0pt"; + $t = 0; + } + + if ( $b === "auto" ) { + $style->margin_bottom = "0pt"; + $b = 0; + } + + // Collapse vertical margins: + $n = $frame->get_next_sibling(); + if ( $n && !$n->is_block() ) { + while ( $n = $n->get_next_sibling() ) { + if ( $n->is_block() ) { + break; + } + + if ( !$n->get_first_child() ) { + $n = null; + break; + } + } + } + + if ( $n ) { + $n_style = $n->get_style(); + $b = max($b, $n_style->length_in_pt($n_style->margin_top, $cb["h"])); + $n_style->margin_top = "0pt"; + $style->margin_bottom = $b."pt"; + } + + // Collapse our first child's margin + /*$f = $this->_frame->get_first_child(); + if ( $f && !$f->is_block() ) { + while ( $f = $f->get_next_sibling() ) { + if ( $f->is_block() ) { + break; + } + + if ( !$f->get_first_child() ) { + $f = null; + break; + } + } + } + + // Margin are collapsed only between block elements + if ( $f ) { + $f_style = $f->get_style(); + $t = max($t, $f_style->length_in_pt($f_style->margin_top, $cb["h"])); + $style->margin_top = $t."pt"; + $f_style->margin_bottom = "0pt"; + }*/ + } + + //........................................................................ + + abstract function reflow(Block_Frame_Decorator $block = null); + + //........................................................................ + + // Required for table layout: Returns an array(0 => min, 1 => max, "min" + // => min, "max" => max) of the minimum and maximum widths of this frame. + // This provides a basic implementation. Child classes should override + // this if necessary. + function get_min_max_width() { + if ( !is_null($this->_min_max_cache) ) { + return $this->_min_max_cache; + } + + $style = $this->_frame->get_style(); + + // Account for margins & padding + $dims = array($style->padding_left, + $style->padding_right, + $style->border_left_width, + $style->border_right_width, + $style->margin_left, + $style->margin_right); + + $cb_w = $this->_frame->get_containing_block("w"); + $delta = $style->length_in_pt($dims, $cb_w); + + // Handle degenerate case + if ( !$this->_frame->get_first_child() ) { + return $this->_min_max_cache = array( + $delta, $delta, + "min" => $delta, + "max" => $delta, + ); + } + + $low = array(); + $high = array(); + + for ( $iter = $this->_frame->get_children()->getIterator(); + $iter->valid(); + $iter->next() ) { + + $inline_min = 0; + $inline_max = 0; + + // Add all adjacent inline widths together to calculate max width + while ( $iter->valid() && in_array( $iter->current()->get_style()->display, Style::$INLINE_TYPES ) ) { + + $child = $iter->current(); + + $minmax = $child->get_min_max_width(); + + if ( in_array( $iter->current()->get_style()->white_space, array("pre", "nowrap") ) ) { + $inline_min += $minmax["min"]; + } + else { + $low[] = $minmax["min"]; + } + + $inline_max += $minmax["max"]; + $iter->next(); + + } + + if ( $inline_max > 0 ) $high[] = $inline_max; + if ( $inline_min > 0 ) $low[] = $inline_min; + + if ( $iter->valid() ) { + list($low[], $high[]) = $iter->current()->get_min_max_width(); + continue; + } + + } + $min = count($low) ? max($low) : 0; + $max = count($high) ? max($high) : 0; + + // Use specified width if it is greater than the minimum defined by the + // content. If the width is a percentage ignore it for now. + $width = $style->width; + if ( $width !== "auto" && !is_percent($width) ) { + $width = $style->length_in_pt($width, $cb_w); + if ( $min < $width ) $min = $width; + if ( $max < $width ) $max = $width; + } + + $min += $delta; + $max += $delta; + return $this->_min_max_cache = array($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(array("\\\n",'\\"',"\\'"), + array("",'"',"'"), $string); + + // Convert escaped hex characters into ascii characters (e.g. \A => newline) + $string = preg_replace_callback("/\\\\([0-9a-fA-F]{0,6})/", + create_function('$matches', + 'return unichr(hexdec($matches[1]));'), + $string); + return $string; + } + + /** + * Parses a CSS "quotes" property + * + * @return array|null An array of pairs of quotes + */ + protected function _parse_quotes() { + + // Matches quote types + $re = '/(\'[^\']*\')|(\"[^\"]*\")/'; + + $quotes = $this->_frame->get_style()->quotes; + + // split on spaces, except within quotes + if ( !preg_match_all($re, "$quotes", $matches, PREG_SET_ORDER) ) { + return null; + } + + $quotes_array = array(); + foreach($matches as &$_quote){ + $quotes_array[] = $this->_parse_string($_quote[0], true); + } + + if ( empty($quotes_array) ) { + $quotes_array = array('"', '"'); + } + + return array_chunk($quotes_array, 2); + } + + /** + * Parses the CSS "content" property + * + * @return string|null The resulting string + */ + protected function _parse_content() { + + // Matches generated content + $re = "/\n". + "\s(counters?\\([^)]*\\))|\n". + "\A(counters?\\([^)]*\\))|\n". + "\s([\"']) ( (?:[^\"']|\\\\[\"'])+ )(?_frame->get_style()->content; + + $quotes = $this->_parse_quotes(); + + // split on spaces, except within quotes + if ( !preg_match_all($re, $content, $matches, PREG_SET_ORDER) ) { + return null; + } + + $text = ""; + + foreach ($matches as $match) { + + if ( isset($match[2]) && $match[2] !== "" ) { + $match[1] = $match[2]; + } + + if ( isset($match[6]) && $match[6] !== "" ) { + $match[4] = $match[6]; + } + + if ( isset($match[8]) && $match[8] !== "" ) { + $match[7] = $match[8]; + } + + if ( isset($match[1]) && $match[1] !== "" ) { + + // counters?(...) + $match[1] = mb_strtolower(trim($match[1])); + + // Handle counter() references: + // http://www.w3.org/TR/CSS21/generate.html#content + + $i = mb_strpos($match[1], ")"); + if ( $i === false ) { + continue; + } + + preg_match( '/(counters?)(^\()*?\(\s*([^\s,]+)\s*(,\s*["\']?([^"\'\)]+)["\']?\s*(,\s*([^\s)]+)\s*)?)?\)/i' , $match[1] , $args ); + $counter_id = $args[3]; + if ( strtolower( $args[1] ) == 'counter' ) { + // counter(name [,style]) + if ( isset( $args[5] ) ) { + $type = trim( $args[5] ); + } + else { + $type = null; + } + $p = $this->_frame->lookup_counter_frame( $counter_id ); + + $text .= $p->counter_value($counter_id, $type); + + } + else if ( 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 = null; + } + + $p = $this->_frame->lookup_counter_frame($counter_id); + $tmp = array(); + 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; + } + + } + else if ( isset($match[4]) && $match[4] !== "" ) { + // String match + $text .= $this->_parse_string($match[4]); + } + else if ( isset($match[7]) && $match[7] !== "" ) { + // Directive match + + if ( $match[7] === "open-quote" ) { + // FIXME: do something here + $text .= $quotes[0][0]; + } + else if ( $match[7] === "close-quote" ) { + // FIXME: do something else here + $text .= $quotes[0][1]; + } + else if ( $match[7] === "no-open-quote" ) { + // FIXME: + } + else if ( $match[7] === "no-close-quote" ) { + // FIXME: + } + else if ( mb_strpos($match[7],"attr(") === 0 ) { + + $i = mb_strpos($match[7],")"); + if ( $i === false ) { + continue; + } + + $attr = mb_substr($match[7], 5, $i - 5); + if ( $attr == "" ) { + continue; + } + + $text .= $this->_frame->get_parent()->get_node()->getAttribute($attr); + } + else { + continue; + } + } + } + + return $text; + } + + /** + * Sets the generated content of a generated frame + */ + protected function _set_content(){ + $frame = $this->_frame; + $style = $frame->get_style(); + + // if the element was pushed to a new page use the saved counter value, otherwise use the CSS reset value + if ( $style->counter_reset && ($reset = $style->counter_reset) !== "none" ) { + $vars = preg_split('/\s+/', trim($reset), 2); + $frame->reset_counter( $vars[0] , ( isset($frame->_counters['__'.$vars[0]]) ? $frame->_counters['__'.$vars[0]] : ( isset($vars[1]) ? $vars[1] : 0 ) ) ); + } + + if ( $style->counter_increment && ($increment = $style->counter_increment) !== "none" ) { + $frame->increment_counters($increment); + } + + if ( $style->content && !$frame->get_first_child() && $frame->get_node()->nodeName === "dompdf_generated" ) { + $content = $this->_parse_content(); + // add generated content to the font subset + // FIXME: This is currently too late because the font subset has already been generated. + // See notes in issue #750. + if ( $frame->get_dompdf()->get_option("enable_font_subsetting") && $frame->get_dompdf()->get_canvas() instanceof CPDF_Adapter ) { + $frame->get_dompdf()->get_canvas()->register_string_subset($style->font_family, $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); + + Frame_Factory::decorate_frame($new_frame, $frame->get_dompdf(), $frame->get_root()); + $frame->append_child($new_frame); + } + } +} diff --git a/library/vendor/dompdf/include/frame_tree.cls.php b/library/vendor/dompdf/include/frame_tree.cls.php new file mode 100644 index 000000000..257ca097e --- /dev/null +++ b/library/vendor/dompdf/include/frame_tree.cls.php @@ -0,0 +1,241 @@ + + * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + */ + +/** + * Represents an entire document as a tree of frames + * + * The Frame_Tree consists of {@link Frame} objects each tied to specific + * DOMNode objects in a specific DomDocument. The Frame_Tree has the same + * structure as the DomDocument, but adds additional capabalities for + * styling and layout. + * + * @package dompdf + * @access protected + */ +class Frame_Tree { + + /** + * Tags to ignore while parsing the tree + * + * @var array + */ + static protected $_HIDDEN_TAGS = array("area", "base", "basefont", "head", "style", + "meta", "title", "colgroup", + "noembed", "noscript", "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 + */ + function __construct(DomDocument $dom) { + $this->_dom = $dom; + $this->_root = null; + $this->_registry = array(); + } + + function __destruct() { + clear_object($this); + } + + /** + * Returns the DomDocument object representing the curent html document + * + * @return DOMDocument + */ + function get_dom() { + return $this->_dom; + } + + /** + * Returns the root frame of the tree + * + * @return Page_Frame_Decorator + */ + function get_root() { + return $this->_root; + } + + /** + * Returns a specific frame given its id + * + * @param string $id + * @return Frame + */ + function get_frame($id) { + return isset($this->_registry[$id]) ? $this->_registry[$id] : null; + } + + /** + * Returns a post-order iterator for all frames in the tree + * + * @return FrameTreeList|Frame[] + */ + function get_frames() { + return new FrameTreeList($this->_root); + } + + /** + * Builds the tree + */ + function build_tree() { + $html = $this->_dom->getElementsByTagName("html")->item(0); + if ( is_null($html) ) { + $html = $this->_dom->firstChild; + } + + if ( is_null($html) ) { + throw new DOMPDF_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); + } + + $rows = $xp->query("//table/tr"); + foreach($rows as $row) { + $tbody = $this->_dom->createElement("tbody"); + $tbody = $row->parentNode->insertBefore($tbody, $row); + $tbody->appendChild($row); + } + } + + /** + * 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; + } + + // Fixes 'cannot access undefined property for object with + // overloaded access', fix by Stefan radulian + // + //foreach ($node->childNodes as $child) { + + // Store the children in an array so that the tree can be modified + $children = array(); + for ($i = 0; $i < $node->childNodes->length; $i++) { + $children[] = $node->childNodes->item($i); + } + + foreach ($children as $child) { + $node_name = mb_strtolower($child->nodeName); + + // Skip non-displaying nodes + if ( in_array($node_name, self::$_HIDDEN_TAGS) ) { + if ( $node_name !== "head" && $node_name !== "style" ) { + $child->parentNode->removeChild($child); + } + + continue; + } + + // Skip empty text nodes + if ( $node_name === "#text" && $child->nodeValue == "" ) { + $child->parentNode->removeChild($child); + continue; + } + + // Skip empty image nodes + if ( $node_name === "img" && $child->getAttribute("src") == "" ) { + $child->parentNode->removeChild($child); + continue; + } + + $frame->append_child($this->_build_tree_r($child), false); + } + + return $frame; + } + + public function insert_node(DOMNode $node, DOMNode $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; + } +} diff --git a/library/vendor/dompdf/include/functions.inc.php b/library/vendor/dompdf/include/functions.inc.php new file mode 100644 index 000000000..8e0ab02c7 --- /dev/null +++ b/library/vendor/dompdf/include/functions.inc.php @@ -0,0 +1,1054 @@ + + * @author Helmut Tischer + * @author Fabien Ménager + * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + */ + +if ( !defined('PHP_VERSION_ID') ) { + $version = explode('.', PHP_VERSION); + define('PHP_VERSION_ID', ($version[0] * 10000 + $version[1] * 100 + $version[2])); +} + +/** + * Defined a constant if not already defined + * + * @param string $name The constant name + * @param mixed $value The value + */ +function def($name, $value = true) { + if ( !defined($name) ) { + define($name, $value); + } +} + +if ( !function_exists("pre_r") ) { +/** + * 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 + */ +function pre_r($mixed, $return = false) { + if ( $return ) { + return "
" . print_r($mixed, true) . "
"; + } + + if ( php_sapi_name() !== "cli" ) { + echo "
";
+  }
+  
+  print_r($mixed);
+
+  if ( php_sapi_name() !== "cli" ) {
+    echo "
"; + } + else { + echo "\n"; + } + + flush(); + +} +} + +if ( !function_exists("pre_var_dump") ) { +/** + * var_dump wrapper for html/cli output + * + * Wraps var_dump() output in < pre > tags if the current sapi is not 'cli'. + * + * @param mixed $mixed variable or expression to display. + */ +function pre_var_dump($mixed) { + if ( php_sapi_name() !== "cli" ) { + echo "
";
+  }
+    
+  var_dump($mixed);
+  
+  if ( php_sapi_name() !== "cli" ) {
+    echo "
"; + } +} +} + +if ( !function_exists("d") ) { +/** + * generic debug function + * + * Takes everything and does its best to give a good debug output + * + * @param mixed $mixed variable or expression to display. + */ +function d($mixed) { + if ( php_sapi_name() !== "cli" ) { + echo "
";
+  }
+    
+  // line
+  if ( $mixed instanceof Line_Box ) {
+    echo $mixed;
+  }
+  
+  // other
+  else {
+    var_export($mixed);
+  }
+  
+  if ( php_sapi_name() !== "cli" ) {
+    echo "
"; + } +} +} + +/** + * 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) + */ +function build_url($protocol, $host, $base_path, $url) { + if ( strlen($url) == 0 ) { + //return $protocol . $host . rtrim($base_path, "/\\") . "/"; + return $protocol . $host . $base_path; + } + + // Is the url already fully qualified or a Data URI? + if ( mb_strpos($url, "://") !== false || mb_strpos($url, "data:") === 0 ) { + return $url; + } + + $ret = $protocol; + + if ( !in_array(mb_strtolower($protocol), array("http://", "https://", "ftp://", "ftps://")) ) { + //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' || ($url[0] !== '\\' && $url[1] !== ':')) ) { + // For rel path and local acess we ignore the host, and run the path through realpath() + $ret .= realpath($base_path).'/'; + } + $ret .= $url; + $ret = preg_replace('/\?(.*)$/', "", $ret); + return $ret; + } + + //remote urls with backslash in html/css are not really correct, but lets be genereous + if ( $url[0] === '/' || $url[0] === '\\' ) { + // Absolute path + $ret .= $host . $url; + } + else { + // Relative path + //$base_path = $base_path !== "" ? rtrim($base_path, "/\\") . "/" : ""; + $ret .= $host . $base_path . $url; + } + + return $ret; + +} + +/** + * parse a full url or pathname and return an array(protocol, host, path, + * file + query + fragment) + * + * @param string $url + * @return array + */ +function explode_url($url) { + $protocol = ""; + $host = ""; + $path = ""; + $file = ""; + + $arr = parse_url($url); + + // Exclude windows drive letters... + if ( isset($arr["scheme"]) && $arr["scheme"] !== "file" && 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 { + + $i = mb_strpos($url, "file://"); + if ( $i !== false ) { + $url = mb_substr($url, $i + 7); + } + + $protocol = ""; // "file://"; ? why doesn't this work... It's because of + // network filenames like //COMPU/SHARENAME + + $host = ""; // localhost, really + $file = basename($url); + + $path = dirname($url); + + // Check that the path exists + if ( $path !== false ) { + $path .= '/'; + + } + else { + // generate a url to access the file if no real path found. + $protocol = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? 'https://' : 'http://'; + + $host = isset($_SERVER["HTTP_HOST"]) ? $_SERVER["HTTP_HOST"] : php_uname("n"); + + if ( substr($arr["path"], 0, 1) === '/' ) { + $path = dirname($arr["path"]); + } + else { + $path = '/' . rtrim(dirname($_SERVER["SCRIPT_NAME"]), '/') . '/' . $arr["path"]; + } + } + } + + $ret = array($protocol, $host, $path, $file, + "protocol" => $protocol, + "host" => $host, + "path" => $path, + "file" => $file); + return $ret; +} + +/** + * Converts decimal numbers to roman numerals + * + * @param int $num + * + * @throws DOMPDF_Exception + * @return string + */ +function dec2roman($num) { + + static $ones = array("", "i", "ii", "iii", "iv", "v", "vi", "vii", "viii", "ix"); + static $tens = array("", "x", "xx", "xxx", "xl", "l", "lx", "lxx", "lxxx", "xc"); + static $hund = array("", "c", "cc", "ccc", "cd", "d", "dc", "dcc", "dccc", "cm"); + static $thou = array("", "m", "mm", "mmm"); + + if ( !is_numeric($num) ) { + throw new DOMPDF_Exception("dec2roman() requires a numeric argument."); + } + + if ( $num > 4000 || $num < 0 ) { + return "(out of range)"; + } + + $num = strrev((string)$num); + + $ret = ""; + switch (mb_strlen($num)) { + case 4: $ret .= $thou[$num[3]]; + case 3: $ret .= $hund[$num[2]]; + case 2: $ret .= $tens[$num[1]]; + case 1: $ret .= $ones[$num[0]]; + default: break; + } + + return $ret; +} + +/** + * Determines whether $value is a percentage or not + * + * @param float $value + * + * @return bool + */ +function is_percent($value) { + return 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 The result with charset, mime type and decoded data + */ +function parse_data_uri($data_uri) { + if (!preg_match('/^data:(?P[a-z0-9\/+-.]+)(;charset=(?P[a-z0-9-])+)?(?P;base64)?\,(?P.*)?/i', $data_uri, $match)) { + return false; + } + + $match['data'] = rawurldecode($match['data']); + $result = array( + '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; +} + +/** + * mb_string compatibility + */ +if (!extension_loaded('mbstring')) { + def('MB_OVERLOAD_MAIL', 1); + def('MB_OVERLOAD_STRING', 2); + def('MB_OVERLOAD_REGEX', 4); + def('MB_CASE_UPPER', 0); + def('MB_CASE_LOWER', 1); + def('MB_CASE_TITLE', 2); + + if (!function_exists('mb_convert_encoding')) { + function mb_convert_encoding($data, $to_encoding, $from_encoding = 'UTF-8') { + if (str_replace('-', '', strtolower($to_encoding)) === 'utf8') { + return utf8_encode($data); + } + + return utf8_decode($data); + } + } + + if (!function_exists('mb_detect_encoding')) { + function mb_detect_encoding($data, $encoding_list = array('iso-8859-1'), $strict = false) { + return 'iso-8859-1'; + } + } + + if (!function_exists('mb_detect_order')) { + function mb_detect_order($encoding_list = array('iso-8859-1')) { + return 'iso-8859-1'; + } + } + + if (!function_exists('mb_internal_encoding')) { + function mb_internal_encoding($encoding = null) { + if (isset($encoding)) { + return true; + } + + return 'iso-8859-1'; + } + } + + if (!function_exists('mb_strlen')) { + function mb_strlen($str, $encoding = 'iso-8859-1') { + switch (str_replace('-', '', strtolower($encoding))) { + case "utf8": return strlen(utf8_encode($str)); + case "8bit": return strlen($str); + default: return strlen(utf8_decode($str)); + } + } + } + + if (!function_exists('mb_strpos')) { + function mb_strpos($haystack, $needle, $offset = 0) { + return strpos($haystack, $needle, $offset); + } + } + + if (!function_exists('mb_strrpos')) { + function mb_strrpos($haystack, $needle, $offset = 0) { + return strrpos($haystack, $needle, $offset); + } + } + + if (!function_exists('mb_strtolower')) { + function mb_strtolower( $str ) { + return strtolower($str); + } + } + + if (!function_exists('mb_strtoupper')) { + function mb_strtoupper( $str ) { + return strtoupper($str); + } + } + + if (!function_exists('mb_substr')) { + function mb_substr($string, $start, $length = null, $encoding = 'iso-8859-1') { + if ( is_null($length) ) { + return substr($string, $start); + } + + return substr($string, $start, $length); + } + } + + if (!function_exists('mb_substr_count')) { + function mb_substr_count($haystack, $needle, $encoding = 'iso-8859-1') { + return substr_count($haystack, $needle); + } + } + + if (!function_exists('mb_encode_numericentity')) { + function mb_encode_numericentity($str, $convmap, $encoding) { + return htmlspecialchars($str); + } + } + + if (!function_exists('mb_convert_case')) { + function mb_convert_case($str, $mode = MB_CASE_UPPER, $encoding = array()) { + switch($mode) { + case MB_CASE_UPPER: return mb_strtoupper($str); + case MB_CASE_LOWER: return mb_strtolower($str); + case MB_CASE_TITLE: return ucwords(mb_strtolower($str)); + default: return $str; + } + } + } + + if (!function_exists('mb_list_encodings')) { + function mb_list_encodings() { + return array( + "ISO-8859-1", + "UTF-8", + "8bit", + ); + } + } +} + +/** + * 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 integer $width Image width + * + * @return string + */ +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 integer $width Image width + * + * @return string + */ +function rle4_decode ($str, $width) { + $w = floor($width/2) + ($width % 2); + $lineWidth = $w + (3 - ( ($width-1) / 2) % 4); + $pixels = array(); + $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; +} + +if ( !function_exists("imagecreatefrombmp") ) { + +/** + * 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 + */ +function imagecreatefrombmp($filename) { + 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 = array(); + 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 = rle8_decode($data, $meta['width']); break; + case 2: $data = 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; +} +} + +/** + * getimagesize doesn't give a good size for 32bit BMP image v5 + * + * @param string $filename + * @return array The same format as getimagesize($filename) + */ +function dompdf_getimagesize($filename) { + static $cache = array(); + + if ( isset($cache[$filename]) ) { + return $cache[$filename]; + } + + list($width, $height, $type) = getimagesize($filename); + + if ( $width == null || $height == null ) { + $data = file_get_contents($filename, null, null, 0, 26); + + 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 = IMAGETYPE_BMP; + } + } + + return $cache[$filename] = array($width, $height, $type); +} + +/** + * Converts a CMYK color to RGB + * + * @param float|float[] $c + * @param float $m + * @param float $y + * @param float $k + * + * @return float[] + */ +function cmyk_to_rgb($c, $m = null, $y = null, $k = null) { + if (is_array($c)) { + list($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 array( + $r, $g, $b, + "r" => $r, "g" => $g, "b" => $b + ); +} + +function unichr($c) { + if ($c <= 0x7F) { + return chr($c); + } + else if ($c <= 0x7FF) { + return chr(0xC0 | $c >> 6) . chr(0x80 | $c & 0x3F); + } + else if ($c <= 0xFFFF) { + return chr(0xE0 | $c >> 12) . chr(0x80 | $c >> 6 & 0x3F) + . chr(0x80 | $c & 0x3F); + } + else if ($c <= 0x10FFFF) { + return chr(0xF0 | $c >> 18) . chr(0x80 | $c >> 12 & 0x3F) + . chr(0x80 | $c >> 6 & 0x3F) + . chr(0x80 | $c & 0x3F); + } + return false; +} + +if ( !function_exists("date_default_timezone_get") ) { + function date_default_timezone_get() { + return ""; + } + + function date_default_timezone_set($timezone_identifier) { + return true; + } +} + +/** + * 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 DOMPDF_Exception + */ +function record_warnings($errno, $errstr, $errfile, $errline) { + + // Not a warning or notice + if ( !($errno & (E_WARNING | E_NOTICE | E_USER_NOTICE | E_USER_WARNING )) ) { + throw new DOMPDF_Exception($errstr . " $errno"); + } + + global $_dompdf_warnings; + global $_dompdf_show_warnings; + + if ( $_dompdf_show_warnings ) { + echo $errstr . "\n"; + } + + $_dompdf_warnings[] = $errstr; +} + +/** + * Print a useful backtrace + */ +function bt() { + if ( php_sapi_name() !== "cli") { + echo "
";
+  }
+    
+  $bt = debug_backtrace();
+
+  array_shift($bt); // remove actual bt() call
+  echo "\n";
+
+  $i = 0;
+  foreach ($bt as $call) {
+    $file = basename($call["file"]) . " (" . $call["line"] . ")";
+    if ( isset($call["class"]) ) {
+      $func = $call["class"] . "->" . $call["function"] . "()";
+    }
+    else {
+      $func = $call["function"] . "()";
+    }
+
+    echo "#" . str_pad($i, 2, " ", STR_PAD_RIGHT) . ": " . str_pad($file.":", 42) . " $func\n";
+    $i++;
+  }
+  echo "\n";
+  
+  if ( php_sapi_name() !== "cli") {
+    echo "
"; + } +} + +/** + * Print debug messages + * + * @param string $type The type of debug messages to print + * @param string $msg The message to show + */ +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"] . ": "; + pre_r($msg); + } +} + +if ( !function_exists("print_memusage") ) { +/** + * Dump memory usage + */ +function print_memusage() { + global $memusage; + echo "Memory Usage\n"; + $prev = 0; + $initial = reset($memusage); + echo str_pad("Initial:", 40) . $initial . "\n\n"; + + foreach ($memusage as $key=>$mem) { + $mem -= $initial; + echo str_pad("$key:" , 40); + echo str_pad("$mem", 12) . "(diff: " . ($mem - $prev) . ")\n"; + $prev = $mem; + } + + echo "\n" . str_pad("Total:", 40) . memory_get_usage() . "\n"; +} +} + +if ( !function_exists("enable_mem_profile") ) { +/** + * Initialize memory profiling code + */ +function enable_mem_profile() { + global $memusage; + $memusage = array("Startup" => memory_get_usage()); + register_shutdown_function("print_memusage"); +} +} + +if ( !function_exists("mark_memusage") ) { +/** + * Record the current memory usage + * + * @param string $location a meaningful location + */ +function mark_memusage($location) { + global $memusage; + if ( isset($memusage) ) { + $memusage[$location] = memory_get_usage(); + } +} +} + +if ( !function_exists('sys_get_temp_dir')) { +/** + * Find the current system temporary directory + * + * @link http://us.php.net/manual/en/function.sys-get-temp-dir.php#85261 + */ +function sys_get_temp_dir() { + if (!empty($_ENV['TMP'])) { + return realpath($_ENV['TMP']); + } + + if (!empty($_ENV['TMPDIR'])) { + return realpath( $_ENV['TMPDIR']); + } + + if (!empty($_ENV['TEMP'])) { + return realpath( $_ENV['TEMP']); + } + + $tempfile=tempnam(uniqid(rand(), true), ''); + if (file_exists($tempfile)) { + unlink($tempfile); + return realpath(dirname($tempfile)); + } +} +} + +if ( function_exists("memory_get_peak_usage") ) { + function DOMPDF_memory_usage(){ + return memory_get_peak_usage(true); + } +} +else if ( function_exists("memory_get_usage") ) { + function DOMPDF_memory_usage(){ + return memory_get_usage(true); + } +} +else { + function DOMPDF_memory_usage(){ + return "N/A"; + } +} + +if ( function_exists("curl_init") ) { + function DOMPDF_fetch_url($url, &$headers = null) { + $ch = curl_init($url); + curl_setopt($ch, CURLOPT_TIMEOUT, 10); + curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_HEADER, true); + + $data = curl_exec($ch); + $raw_headers = substr($data, 0, curl_getinfo($ch, CURLINFO_HEADER_SIZE)); + $headers = preg_split("/[\n\r]+/", trim($raw_headers)); + $data = substr($data, curl_getinfo($ch, CURLINFO_HEADER_SIZE)); + curl_close($ch); + + return $data; + } +} +else { + function DOMPDF_fetch_url($url, &$headers = null) { + $data = file_get_contents($url); + $headers = $http_response_header; + + return $data; + } +} + +/** + * Affect null to the unused objects + * @param mixed $object + */ +if ( PHP_VERSION_ID < 50300 ) { + function clear_object(&$object) { + if ( is_object($object) ) { + foreach ($object as &$value) { + clear_object($value); + } + } + + $object = null; + unset($object); + } +} +else { + function clear_object(&$object) { + // void + } +} diff --git a/library/vendor/dompdf/include/gd_adapter.cls.php b/library/vendor/dompdf/include/gd_adapter.cls.php new file mode 100644 index 000000000..de5c1e452 --- /dev/null +++ b/library/vendor/dompdf/include/gd_adapter.cls.php @@ -0,0 +1,840 @@ + + * @author Fabien Ménager + * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + */ + +/** + * 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_Adapter implements Canvas { + /** + * @var DOMPDF + */ + private $_dompdf; + + /** + * Resource handle for the image + * + * @var resource + */ + private $_img; + + /** + * Image width in pixels + * + * @var int + */ + private $_width; + + /** + * Image height in pixels + * + * @var int + */ + private $_height; + + /** + * Current page number + * + * @var int + */ + private $_page_number; + + /** + * Total number of pages + * + * @var int + */ + private $_page_count; + + /** + * Image antialias factor + * + * @var float + */ + private $_aa_factor; + + /** + * Allocated colors + * + * @var array + */ + private $_colors; + + /** + * Background color + * + * @var int + */ + private $_bg_color; + + /** + * Class constructor + * + * @param mixed $size The size of image to create: array(x1,y1,x2,y2) or "letter", "legal", etc. + * @param string $orientation The orientation of the document (either 'landscape' or 'portrait') + * @param DOMPDF $dompdf + * @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 + */ + function __construct($size, $orientation = "portrait", DOMPDF $dompdf, $aa_factor = 1.0, $bg_color = array(1,1,1,0) ) { + + if ( !is_array($size) ) { + $size = strtolower($size); + + if ( isset(CPDF_Adapter::$PAPER_SIZES[$size]) ) { + $size = CPDF_Adapter::$PAPER_SIZES[$size]; + } + else { + $size = CPDF_Adapter::$PAPER_SIZES["letter"]; + } + } + + if ( strtolower($orientation) === "landscape" ) { + list($size[2],$size[3]) = array($size[3],$size[2]); + } + + $this->_dompdf = $dompdf; + + 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->_img = imagecreatetruecolor($this->_width, $this->_height); + + if ( is_null($bg_color) || !is_array($bg_color) ) { + // Pure white bg + $bg_color = array(1,1,1,0); + } + + $this->_bg_color = $this->_allocate_color($bg_color); + imagealphablending($this->_img, true); + imagesavealpha($this->_img, true); + imagefill($this->_img, 0, 0, $this->_bg_color); + + } + + function get_dompdf(){ + return $this->_dompdf; + } + + /** + * Return the GF image resource + * + * @return resource + */ + function get_image() { return $this->_img; } + + /** + * Return the image's width in pixels + * + * @return float + */ + function get_width() { return $this->_width / $this->_aa_factor; } + + /** + * Return the image's height in pixels + * + * @return float + */ + function get_height() { return $this->_height / $this->_aa_factor; } + + /** + * Returns the current page number + * @return int + */ + function get_page_number() { return $this->_page_number; } + + /** + * Returns the total number of pages in the document + * @return int + */ + function get_page_count() { return $this->_page_count; } + + /** + * Sets the current page number + * + * @param int $num + */ + function set_page_number($num) { $this->_page_number = $num; } + + /** + * Sets the page count + * + * @param int $count + */ + function set_page_count($count) { $this->_page_count = $count; } + + /** + * Sets the opacity + * + * @param $opacity + * @param $mode + */ + function set_opacity($opacity, $mode = "Normal") { + // 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 + */ + private function _allocate_color($color) { + + if ( isset($color["c"]) ) { + $color = cmyk_to_rgb($color); + } + + // Full opacity if no alpha set + if ( !isset($color[3]) ) + $color[3] = 0; + + list($r,$g,$b,$a) = $color; + + $r *= 255; + $g *= 255; + $b *= 255; + $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->_img, $r, $g, $b, $a); + else + $this->_colors[$key] = imagecolorallocate($this->_img, $r, $g, $b); + + return $this->_colors[$key]; + + } + + /** + * Draws a line from x1,y1 to x2,y2 + * + * See {@link Style::munge_color()} for the format of the color array. + * See {@link Cpdf::setLineStyle()} for a description of the format of the + * $style parameter (aka dash). + * + * @param float $x1 + * @param float $y1 + * @param float $x2 + * @param float $y2 + * @param array $color + * @param float $width + * @param array $style + */ + function line($x1, $y1, $x2, $y2, $color, $width, $style = null) { + + // Scale by the AA factor + $x1 *= $this->_aa_factor; + $y1 *= $this->_aa_factor; + $x2 *= $this->_aa_factor; + $y2 *= $this->_aa_factor; + $width *= $this->_aa_factor; + + $c = $this->_allocate_color($color); + + // Convert the style array if required + if ( !is_null($style) ) { + $gd_style = array(); + + if ( count($style) == 1 ) { + for ($i = 0; $i < $style[0] * $this->_aa_factor; $i++) { + $gd_style[] = $c; + } + + for ($i = 0; $i < $style[0] * $this->_aa_factor; $i++) { + $gd_style[] = $this->_bg_color; + } + + } else { + + $i = 0; + foreach ($style as $length) { + + if ( $i % 2 == 0 ) { + // 'On' pattern + for ($i = 0; $i < $style[0] * $this->_aa_factor; $i++) + $gd_style[] = $c; + + } else { + // Off pattern + for ($i = 0; $i < $style[0] * $this->_aa_factor; $i++) + $gd_style[] = $this->_bg_color; + + } + $i++; + } + } + + imagesetstyle($this->_img, $gd_style); + $c = IMG_COLOR_STYLED; + } + + imagesetthickness($this->_img, $width); + + imageline($this->_img, $x1, $y1, $x2, $y2, $c); + + } + + function arc($x1, $y1, $r1, $r2, $astart, $aend, $color, $width, $style = array()) { + // @todo + } + + /** + * Draws a rectangle at x1,y1 with width w and height h + * + * See {@link Style::munge_color()} for the format of the color array. + * See {@link Cpdf::setLineStyle()} for a description of the $style + * parameter (aka dash) + * + * @param float $x1 + * @param float $y1 + * @param float $w + * @param float $h + * @param array $color + * @param float $width + * @param array $style + */ + function rectangle($x1, $y1, $w, $h, $color, $width, $style = null) { + + // Scale by the AA factor + $x1 *= $this->_aa_factor; + $y1 *= $this->_aa_factor; + $w *= $this->_aa_factor; + $h *= $this->_aa_factor; + + $c = $this->_allocate_color($color); + + // Convert the style array if required + if ( !is_null($style) ) { + $gd_style = array(); + + foreach ($style as $length) { + for ($i = 0; $i < $length; $i++) { + $gd_style[] = $c; + } + } + + imagesetstyle($this->_img, $gd_style); + $c = IMG_COLOR_STYLED; + } + + imagesetthickness($this->_img, $width); + + imagerectangle($this->_img, $x1, $y1, $x1 + $w, $y1 + $h, $c); + + } + + /** + * Draws a filled rectangle at x1,y1 with width w and height h + * + * See {@link Style::munge_color()} for the format of the color array. + * + * @param float $x1 + * @param float $y1 + * @param float $w + * @param float $h + * @param array $color + */ + function filled_rectangle($x1, $y1, $w, $h, $color) { + + // Scale by the AA factor + $x1 *= $this->_aa_factor; + $y1 *= $this->_aa_factor; + $w *= $this->_aa_factor; + $h *= $this->_aa_factor; + + $c = $this->_allocate_color($color); + + imagefilledrectangle($this->_img, $x1, $y1, $x1 + $w, $y1 + $h, $c); + + } + + /** + * 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) { + // @todo + } + + function clipping_roundrectangle($x1, $y1, $w, $h, $rTL, $rTR, $rBR, $rBL) { + // @todo + } + + /** + * Ends the last clipping shape + */ + function clipping_end() { + // @todo + } + + function save() { + // @todo + } + + function restore() { + // @todo + } + + function rotate($angle, $x, $y) { + // @todo + } + + function skew($angle_x, $angle_y, $x, $y) { + // @todo + } + + function scale($s_x, $s_y, $x, $y) { + // @todo + } + + function translate($t_x, $t_y) { + // @todo + } + + function transform($a, $b, $c, $d, $e, $f) { + // @todo + } + + /** + * 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 Style::munge_color()} for the format of the color array. + * See {@link Cpdf::setLineStyle()} for a description of the $style + * parameter (aka dash) + * + * @param array $points + * @param array $color + * @param float $width + * @param array $style + * @param bool $fill Fills the polygon if true + */ + function polygon($points, $color, $width = null, $style = null, $fill = false) { + + // Scale each point by the AA factor + foreach (array_keys($points) as $i) + $points[$i] *= $this->_aa_factor; + + $c = $this->_allocate_color($color); + + // Convert the style array if required + if ( !is_null($style) && !$fill ) { + $gd_style = array(); + + foreach ($style as $length) { + for ($i = 0; $i < $length; $i++) { + $gd_style[] = $c; + } + } + + imagesetstyle($this->_img, $gd_style); + $c = IMG_COLOR_STYLED; + } + + imagesetthickness($this->_img, $width); + + if ( $fill ) + imagefilledpolygon($this->_img, $points, count($points) / 2, $c); + else + imagepolygon($this->_img, $points, count($points) / 2, $c); + + } + + /** + * Draws a circle at $x,$y with radius $r + * + * See {@link Style::munge_color()} for the format of the color array. + * See {@link Cpdf::setLineStyle()} for a description of the $style + * parameter (aka dash) + * + * @param float $x + * @param float $y + * @param float $r + * @param array $color + * @param float $width + * @param array $style + * @param bool $fill Fills the circle if true + */ + function circle($x, $y, $r, $color, $width = null, $style = null, $fill = false) { + + // Scale by the AA factor + $x *= $this->_aa_factor; + $y *= $this->_aa_factor; + $r *= $this->_aa_factor; + + $c = $this->_allocate_color($color); + + // Convert the style array if required + if ( !is_null($style) && !$fill ) { + $gd_style = array(); + + foreach ($style as $length) { + for ($i = 0; $i < $length; $i++) { + $gd_style[] = $c; + } + } + + imagesetstyle($this->_img, $gd_style); + $c = IMG_COLOR_STYLED; + } + + imagesetthickness($this->_img, $width); + + if ( $fill ) + imagefilledellipse($this->_img, $x, $y, $r, $r, $c); + else + imageellipse($this->_img, $x, $y, $r, $r, $c); + + } + + /** + * 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_url the path to the image + * @param float $x x position + * @param float $y y position + * @param int $w width (in pixels) + * @param int $h height (in pixels) + * @param string $resolution + * + * @return void + * @internal param string $img_type the type (e.g. extension) of the image + */ + function image($img_url, $x, $y, $w, $h, $resolution = "normal") { + $img_type = Image_Cache::detect_type($img_url); + $img_ext = Image_Cache::type_to_ext($img_type); + + if ( !$img_ext ) { + return; + } + + $func = "imagecreatefrom$img_ext"; + $src = @$func($img_url); + + if ( !$src ) { + return; // Probably should add to $_dompdf_errors or whatever here + } + + // Scale by the AA factor + $x *= $this->_aa_factor; + $y *= $this->_aa_factor; + + $w *= $this->_aa_factor; + $h *= $this->_aa_factor; + + $img_w = imagesx($src); + $img_h = imagesy($src); + + imagecopyresampled($this->_img, $src, $x, $y, 0, 0, $w, $h, $img_w, $img_h); + + } + + /** + * Writes text at the specified x and y coordinates + * See {@link Style::munge_color()} for the format of the color array. + * + * @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 + * @param float $word_spacing word spacing adjustment + * @param float $char_spacing + * @param float $angle Text angle + * + * @return void + */ + function text($x, $y, $text, $font, $size, $color = array(0,0,0), $word_spacing = 0.0, $char_spacing = 0.0, $angle = 0.0) { + + // Scale by the AA factor + $x *= $this->_aa_factor; + $y *= $this->_aa_factor; + $size *= $this->_aa_factor; + + $h = $this->get_font_height($font, $size); + $c = $this->_allocate_color($color); + + $text = mb_encode_numericentity($text, array(0x0080, 0xff, 0, 0xff), 'UTF-8'); + + $font = $this->get_ttf_file($font); + + // FIXME: word spacing + @imagettftext($this->_img, $size, $angle, $x, $y + $h, $c, $font, $text); + + } + + function javascript($code) { + // Not implemented + } + + /** + * Add a named destination (similar to ... in html) + * + * @param string $anchorname The name of the named destination + */ + function add_named_dest($anchorname) { + // Not implemented + } + + /** + * 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) { + // Not implemented + } + + /** + * Add meta information to the PDF + * + * @param string $label label of the value (Creator, Producer, etc.) + * @param string $value the text to set + */ + function add_info($label, $value) { + // N/A + } + + function set_default_view($view, $options = array()) { + // N/A + } + + /** + * Calculates text size, in points + * + * @param string $text the text to be sized + * @param string $font the desired font + * @param float $size the desired font size + * @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) { + $font = $this->get_ttf_file($font); + + $text = mb_encode_numericentity($text, array(0x0080, 0xffff, 0, 0xffff), 'UTF-8'); + + // FIXME: word spacing + list($x1,,$x2) = @imagettfbbox($size, 0, $font, $text); + return $x2 - $x1; + } + + function get_ttf_file($font) { + if ( strpos($font, '.ttf') === false ) + $font .= ".ttf"; + + /*$filename = substr(strtolower(basename($font)), 0, -4); + + if ( in_array($filename, DOMPDF::$native_fonts) ) { + return "arial.ttf"; + }*/ + + return $font; + } + + /** + * Calculates font height, in points + * + * @param string $font + * @param float $size + * @return float + */ + function get_font_height($font, $size) { + $font = $this->get_ttf_file($font); + $ratio = $this->_dompdf->get_option("font_height_ratio"); + + // FIXME: word spacing + list(,$y2,,,,$y1) = imagettfbbox($size, 0, $font, "MXjpqytfhl"); // Test string with ascenders, descenders and caps + return ($y2 - $y1) * $ratio; + } + + function get_font_baseline($font, $size) { + $ratio = $this->_dompdf->get_option("font_height_ratio"); + return $this->get_font_height($font, $size) / $ratio; + } + + /** + * Starts a new page + * + * Subsequent drawing operations will appear on the new page. + */ + function new_page() { + $this->_page_number++; + $this->_page_count++; + } + + function open_object(){ + // N/A + } + + function close_object(){ + // N/A + } + + function add_object(){ + // N/A + } + + function page_text(){ + // N/A + } + + /** + * Streams the image directly to the browser + * + * @param string $filename the name of the image file (ignored) + * @param array $options associative array, 'type' => jpeg|jpg|png, 'quality' => 0 - 100 (jpeg only) + */ + function stream($filename, $options = null) { + + // Perform any antialiasing + if ( $this->_aa_factor != 1 ) { + $dst_w = $this->_width / $this->_aa_factor; + $dst_h = $this->_height / $this->_aa_factor; + $dst = imagecreatetruecolor($dst_w, $dst_h); + imagecopyresampled($dst, $this->_img, 0, 0, 0, 0, + $dst_w, $dst_h, + $this->_width, $this->_height); + } else { + $dst = $this->_img; + } + + if ( !isset($options["type"]) ) + $options["type"] = "png"; + + $type = strtolower($options["type"]); + + header("Cache-Control: private"); + + switch ($type) { + + case "jpg": + case "jpeg": + if ( !isset($options["quality"]) ) + $options["quality"] = 75; + + header("Content-type: image/jpeg"); + imagejpeg($dst, '', $options["quality"]); + break; + + case "png": + default: + header("Content-type: image/png"); + imagepng($dst); + break; + } + + if ( $this->_aa_factor != 1 ) + imagedestroy($dst); + } + + /** + * Returns the PNG as a string + * + * @param array $options associative array, 'type' => jpeg|jpg|png, 'quality' => 0 - 100 (jpeg only) + * @return string + */ + function output($options = null) { + + if ( $this->_aa_factor != 1 ) { + $dst_w = $this->_width / $this->_aa_factor; + $dst_h = $this->_height / $this->_aa_factor; + $dst = imagecreatetruecolor($dst_w, $dst_h); + imagecopyresampled($dst, $this->_img, 0, 0, 0, 0, + $dst_w, $dst_h, + $this->_width, $this->_height); + } else { + $dst = $this->_img; + } + + if ( !isset($options["type"]) ) + $options["type"] = "png"; + + $type = $options["type"]; + + ob_start(); + + switch ($type) { + + case "jpg": + case "jpeg": + if ( !isset($options["quality"]) ) + $options["quality"] = 75; + + imagejpeg($dst, '', $options["quality"]); + break; + + case "png": + default: + imagepng($dst); + break; + } + + $image = ob_get_clean(); + + if ( $this->_aa_factor != 1 ) + imagedestroy($dst); + + return $image; + } + + +} diff --git a/library/vendor/dompdf/include/image_cache.cls.php b/library/vendor/dompdf/include/image_cache.cls.php new file mode 100644 index 000000000..7d7e5603b --- /dev/null +++ b/library/vendor/dompdf/include/image_cache.cls.php @@ -0,0 +1,183 @@ + + * @author Helmut Tischer + * @author Fabien Ménager + * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + */ + +/** + * Static class that resolves image urls and downloads and caches + * remote images if required. + * + * @access private + * @package dompdf + */ +class Image_Cache { + + /** + * Array of downloaded images. Cached so that identical images are + * not needlessly downloaded. + * + * @var array + */ + static protected $_cache = array(); + + /** + * The url to the "broken image" used when images can't be loade + * + * @var string + */ + public static $broken_image; + + /** + * 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 DOMPDF $dompdf The DOMPDF instance + * + * @throws DOMPDF_Image_Exception + * @return array An array with two elements: The local path to the image and the image extension + */ + static function resolve_url($url, $protocol, $host, $base_path, DOMPDF $dompdf) { + $parsed_url = explode_url($url); + $message = null; + + $remote = ($protocol && $protocol !== "file://") || ($parsed_url['protocol'] != ""); + + $data_uri = strpos($parsed_url['protocol'], "data:") === 0; + $full_url = null; + $enable_remote = $dompdf->get_option("enable_remote"); + + try { + + // Remote not allowed and is not DataURI + if ( !$enable_remote && $remote && !$data_uri ) { + throw new DOMPDF_Image_Exception("DOMPDF_ENABLE_REMOTE is set to FALSE"); + } + + // Remote allowed or DataURI + else if ( $enable_remote && $remote || $data_uri ) { + // Download remote files to a temporary directory + $full_url = build_url($protocol, $host, $base_path, $url); + + // From cache + if ( isset(self::$_cache[$full_url]) ) { + $resolved_url = self::$_cache[$full_url]; + } + + // From remote + else { + $tmp_dir = $dompdf->get_option("temp_dir"); + $resolved_url = tempnam($tmp_dir, "ca_dompdf_img_"); + $image = ""; + + if ($data_uri) { + if ($parsed_data_uri = parse_data_uri($url)) { + $image = $parsed_data_uri['data']; + } + } + else { + set_error_handler("record_warnings"); + $image = file_get_contents($full_url); + restore_error_handler(); + } + + // Image not found or invalid + if ( strlen($image) == 0 ) { + $msg = ($data_uri ? "Data-URI could not be parsed" : "Image not found"); + throw new DOMPDF_Image_Exception($msg); + } + + // Image found, put in cache and process + else { + //e.g. fetch.php?media=url.jpg&cache=1 + //- Image file name might be one of the dynamic parts of the url, don't strip off! + //- a remote url does not need to have a file extension at all + //- local cached file does not have a matching file extension + //Therefore get image type from the content + file_put_contents($resolved_url, $image); + } + } + } + + // Not remote, local image + else { + $resolved_url = build_url($protocol, $host, $base_path, $url); + } + + // Check if the local file is readable + if ( !is_readable($resolved_url) || !filesize($resolved_url) ) { + throw new DOMPDF_Image_Exception("Image not readable or empty"); + } + + // Check is the file is an image + else { + list($width, $height, $type) = dompdf_getimagesize($resolved_url); + + // Known image type + if ( $width && $height && in_array($type, array(IMAGETYPE_GIF, IMAGETYPE_PNG, IMAGETYPE_JPEG, IMAGETYPE_BMP)) ) { + //Don't put replacement image into cache - otherwise it will be deleted on cache cleanup. + //Only execute on successful caching of remote image. + if ( $enable_remote && $remote || $data_uri ) { + self::$_cache[$full_url] = $resolved_url; + } + } + + // Unknown image type + else { + throw new DOMPDF_Image_Exception("Image type unknown"); + } + } + } + catch(DOMPDF_Image_Exception $e) { + $resolved_url = self::$broken_image; + $type = IMAGETYPE_PNG; + $message = $e->getMessage()." \n $url"; + } + + return array($resolved_url, $type, $message); + } + + /** + * Unlink all cached images (i.e. temporary images either downloaded + * or converted) + */ + static function clear() { + if ( empty(self::$_cache) || DEBUGKEEPTEMP ) return; + + foreach ( self::$_cache as $file ) { + if (DEBUGPNG) print "[clear unlink $file]"; + unlink($file); + } + + self::$_cache = array(); + } + + static function detect_type($file) { + list(, , $type) = dompdf_getimagesize($file); + return $type; + } + + static function type_to_ext($type) { + $image_types = array( + IMAGETYPE_GIF => "gif", + IMAGETYPE_PNG => "png", + IMAGETYPE_JPEG => "jpeg", + IMAGETYPE_BMP => "bmp", + ); + + return (isset($image_types[$type]) ? $image_types[$type] : null); + } + + static function is_broken($url) { + return $url === self::$broken_image; + } +} + +Image_Cache::$broken_image = DOMPDF_LIB_DIR . "/res/broken_image.png"; diff --git a/library/vendor/dompdf/include/image_frame_decorator.cls.php b/library/vendor/dompdf/include/image_frame_decorator.cls.php new file mode 100644 index 000000000..b5a7983aa --- /dev/null +++ b/library/vendor/dompdf/include/image_frame_decorator.cls.php @@ -0,0 +1,80 @@ + + * @author Fabien Ménager + * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + */ + +/** + * Decorates frames for image layout and rendering + * + * @access private + * @package dompdf + */ +class Image_Frame_Decorator extends Frame_Decorator { + + /** + * The path to the image file (note that remote images are + * downloaded locally to DOMPDF_TEMP_DIR). + * + * @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->get_option("debug_png"); + if ($debug_png) print '[__construct '.$url.']'; + + list($this->_image_url, /*$type*/, $this->_image_msg) = Image_Cache::resolve_url( + $url, + $dompdf->get_protocol(), + $dompdf->get_host(), + $dompdf->get_base_path(), + $dompdf + ); + + if ( Image_Cache::is_broken($this->_image_url) && + $alt = $frame->get_node()->getAttribute("alt") ) { + $style = $frame->get_style(); + $style->width = (4/3)*Font_Metrics::get_text_width($alt, $style->font_family, $style->font_size, $style->word_spacing); + $style->height = Font_Metrics::get_font_height($style->font_family, $style->font_size); + } + } + + /** + * 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; + } + +} diff --git a/library/vendor/dompdf/include/image_frame_reflower.cls.php b/library/vendor/dompdf/include/image_frame_reflower.cls.php new file mode 100644 index 000000000..5797b8243 --- /dev/null +++ b/library/vendor/dompdf/include/image_frame_reflower.cls.php @@ -0,0 +1,186 @@ + + * @author Fabien Ménager + * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + */ + +/** + * Image reflower class + * + * @access private + * @package dompdf + */ +class Image_Frame_Reflower extends Frame_Reflower { + + function __construct(Image_Frame_Decorator $frame) { + parent::__construct($frame); + } + + function reflow(Block_Frame_Decorator $block = null) { + $this->_frame->position(); + + //FLOAT + //$frame = $this->_frame; + //$page = $frame->get_root(); + + //$enable_css_float = $this->get_dompdf()->get_option("enable_css_float"); + //if ($enable_css_float && $frame->get_style()->float !== "none" ) { + // $page->add_floating_frame($this); + //} + // Set the frame's width + $this->get_min_max_width(); + + if ( $block ) { + $block->add_frame_to_line($this->_frame); + } + } + + function get_min_max_width() { + if (DEBUGPNG) { + // Determine the image's size. Time consuming. Only when really needed? + list($img_width, $img_height) = dompdf_getimagesize($this->_frame->get_image_url()); + print "get_min_max_width() ". + $this->_frame->get_style()->width.' '. + $this->_frame->get_style()->height.';'. + $this->_frame->get_parent()->get_style()->width." ". + $this->_frame->get_parent()->get_style()->height.";". + $this->_frame->get_parent()->get_parent()->get_style()->width.' '. + $this->_frame->get_parent()->get_parent()->get_style()->height.';'. + $img_width. ' '. + $img_height.'|' ; + } + + $style = $this->_frame->get_style(); + + $width_forced = true; + $height_forced = true; + + //own style auto or invalid value: use natural size in px + //own style value: ignore suffix text including unit, use given number as px + //own style %: walk up parent chain until found available space in pt; fill available space + // + //special ignored unit: e.g. 10ex: e treated as exponent; x ignored; 10e completely invalid ->like auto + + $width = ($style->width > 0 ? $style->width : 0); + if ( is_percent($width) ) { + $t = 0.0; + for ($f = $this->_frame->get_parent(); $f; $f = $f->get_parent()) { + $f_style = $f->get_style(); + $t = $f_style->length_in_pt($f_style->width); + if ($t != 0) { + break; + } + } + $width = ((float)rtrim($width,"%") * $t)/100; //maybe 0 + } elseif ( !mb_strpos($width, 'pt') ) { + // Don't set image original size if "%" branch was 0 or size not given. + // Otherwise aspect changed on %/auto combination for width/height + // Resample according to px per inch + // See also List_Bullet_Image_Frame_Decorator::__construct + $width = $style->length_in_pt($width); + } + + $height = ($style->height > 0 ? $style->height : 0); + if ( is_percent($height) ) { + $t = 0.0; + for ($f = $this->_frame->get_parent(); $f; $f = $f->get_parent()) { + $f_style = $f->get_style(); + $t = $f_style->length_in_pt($f_style->height); + if ($t != 0) { + break; + } + } + $height = ((float)rtrim($height,"%") * $t)/100; //maybe 0 + } elseif ( !mb_strpos($height, 'pt') ) { + // Don't set image original size if "%" branch was 0 or size not given. + // Otherwise aspect changed on %/auto combination for width/height + // Resample according to px per inch + // See also List_Bullet_Image_Frame_Decorator::__construct + $height = $style->length_in_pt($height); + } + + if ($width == 0 || $height == 0) { + // Determine the image's size. Time consuming. Only when really needed! + list($img_width, $img_height) = dompdf_getimagesize($this->_frame->get_image_url()); + + // don't treat 0 as error. Can be downscaled or can be catched elsewhere if image not readable. + // Resample according to px per inch + // See also List_Bullet_Image_Frame_Decorator::__construct + if ($width == 0 && $height == 0) { + $dpi = $this->_frame->get_dompdf()->get_option("dpi"); + $width = (float)($img_width * 72) / $dpi; + $height = (float)($img_height * 72) / $dpi; + $width_forced = false; + $height_forced = false; + } elseif ($height == 0 && $width != 0) { + $height_forced = false; + $height = ($width / $img_width) * $img_height; //keep aspect ratio + } elseif ($width == 0 && $height != 0) { + $width_forced = false; + $width = ($height / $img_height) * $img_width; //keep aspect ratio + } + } + + // Handle min/max width/height + if ( $style->min_width !== "none" || + $style->max_width !== "none" || + $style->min_height !== "none" || + $style->max_height !== "none" ) { + + list(/*$x*/, /*$y*/, $w, $h) = $this->_frame->get_containing_block(); + + $min_width = $style->length_in_pt($style->min_width, $w); + $max_width = $style->length_in_pt($style->max_width, $w); + $min_height = $style->length_in_pt($style->min_height, $h); + $max_height = $style->length_in_pt($style->max_height, $h); + + if ( $max_width !== "none" && $width > $max_width ) { + if ( !$height_forced ) { + $height *= $max_width / $width; + } + + $width = $max_width; + } + + if ( $min_width !== "none" && $width < $min_width ) { + if ( !$height_forced ) { + $height *= $min_width / $width; + } + + $width = $min_width; + } + + if ( $max_height !== "none" && $height > $max_height ) { + if ( !$width_forced ) { + $width *= $max_height / $height; + } + + $height = $max_height; + } + + if ( $min_height !== "none" && $height < $min_height ) { + if ( !$width_forced ) { + $width *= $min_height / $height; + } + + $height = $min_height; + } + } + + if (DEBUGPNG) print $width.' '.$height.';'; + + $style->width = $width . "pt"; + $style->height = $height . "pt"; + + $style->min_width = "none"; + $style->max_width = "none"; + $style->min_height = "none"; + $style->max_height = "none"; + + return array( $width, $width, "min" => $width, "max" => $width); + + } +} diff --git a/library/vendor/dompdf/include/image_renderer.cls.php b/library/vendor/dompdf/include/image_renderer.cls.php new file mode 100644 index 000000000..561b70153 --- /dev/null +++ b/library/vendor/dompdf/include/image_renderer.cls.php @@ -0,0 +1,119 @@ + + * @author Fabien Ménager + * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + */ + +/** + * Image renderer + * + * @access private + * @package dompdf + */ +class Image_Renderer extends Block_Renderer { + + function render(Frame $frame) { + // Render background & borders + $style = $frame->get_style(); + $cb = $frame->get_containing_block(); + list($x, $y, $w, $h) = $frame->get_border_box(); + + $this->_set_opacity( $frame->get_opacity( $style->opacity ) ); + + list($tl, $tr, $br, $bl) = $style->get_computed_border_radius($w, $h); + + $has_border_radius = $tl + $tr + $br + $bl > 0; + + if ( $has_border_radius ) { + $this->_canvas->clipping_roundrectangle( $x, $y, $w, $h, $tl, $tr, $br, $bl ); + } + + if ( ($bg = $style->background_color) !== "transparent" ) { + $this->_canvas->filled_rectangle($x, $y, $w, $h, $bg); + } + + if ( ($url = $style->background_image) && $url !== "none" ) { + $this->_background_image($url, $x, $y, $w, $h, $style); + } + + if ( $has_border_radius ) { + $this->_canvas->clipping_end(); + } + + $this->_render_border($frame); + $this->_render_outline($frame); + + list($x, $y) = $frame->get_padding_box(); + + $x += $style->length_in_pt($style->padding_left, $cb["w"]); + $y += $style->length_in_pt($style->padding_top, $cb["h"]); + + $w = $style->length_in_pt($style->width, $cb["w"]); + $h = $style->length_in_pt($style->height, $cb["h"]); + + if ( $has_border_radius ) { + list($wt, $wr, $wb, $wl) = array( + $style->border_top_width, + $style->border_right_width, + $style->border_bottom_width, + $style->border_left_width, + ); + + // we have to get the "inner" radius + if ( $tl > 0 ) { + $tl -= ($wt + $wl) / 2; + } + if ( $tr > 0 ) { + $tr -= ($wt + $wr) / 2; + } + if ( $br > 0 ) { + $br -= ($wb + $wr) / 2; + } + if ( $bl > 0 ) { + $bl -= ($wb + $wl) / 2; + } + + $this->_canvas->clipping_roundrectangle( $x, $y, $w, $h, $tl, $tr, $br, $bl ); + } + + $src = $frame->get_image_url(); + $alt = null; + + if ( Image_Cache::is_broken($src) && + $alt = $frame->get_node()->getAttribute("alt") ) { + $font = $style->font_family; + $size = $style->font_size; + $spacing = $style->word_spacing; + $this->_canvas->text($x, $y, $alt, + $font, $size, + $style->color, $spacing); + } + else { + $this->_canvas->image( $src, $x, $y, $w, $h, $style->image_resolution); + } + + if ( $has_border_radius ) { + $this->_canvas->clipping_end(); + } + + if ( $msg = $frame->get_image_msg() ) { + $parts = preg_split("/\s*\n\s*/", $msg); + $height = 10; + $_y = $alt ? $y+$h-count($parts)*$height : $y; + + foreach($parts as $i => $_part) { + $this->_canvas->text($x, $_y + $i*$height, $_part, "times", $height*0.8, array(0.5, 0.5, 0.5)); + } + } + + if (DEBUG_LAYOUT && DEBUG_LAYOUT_BLOCKS) { + $this->_debug_layout($frame->get_border_box(), "blue"); + if (DEBUG_LAYOUT_PADDINGBOX) { + $this->_debug_layout($frame->get_padding_box(), "blue", array(0.5, 0.5)); + } + } + } +} diff --git a/library/vendor/dompdf/include/inline_frame_decorator.cls.php b/library/vendor/dompdf/include/inline_frame_decorator.cls.php new file mode 100644 index 000000000..ce79bab08 --- /dev/null +++ b/library/vendor/dompdf/include/inline_frame_decorator.cls.php @@ -0,0 +1,74 @@ + + * @author Helmut Tischer + * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + */ + +/** + * Decorates frames for inline layout + * + * @access private + * @package dompdf + */ +class Inline_Frame_Decorator extends Frame_Decorator { + + function __construct(Frame $frame, DOMPDF $dompdf) { parent::__construct($frame, $dompdf); } + + function split(Frame $frame = null, $force_pagebreak = false) { + + if ( is_null($frame) ) { + $this->get_parent()->split($this, $force_pagebreak); + return; + } + + if ( $frame->get_parent() !== $this ) + throw new DOMPDF_Exception("Unable to split: frame is not a child of this one."); + + $split = $this->copy( $this->_frame->get_node()->cloneNode() ); + $this->get_parent()->insert_child_after($split, $this); + + // Unset the current node's right style properties + $style = $this->_frame->get_style(); + $style->margin_right = 0; + $style->padding_right = 0; + $style->border_right_width = 0; + + // Unset the split node's left style properties since we don't want them + // to propagate + $style = $split->get_style(); + $style->margin_left = 0; + $style->padding_left = 0; + $style->border_left_width = 0; + + //On continuation of inline element on next line, + //don't repeat non-vertically repeatble background images + //See e.g. in testcase image_variants, long desriptions + if ( ($url = $style->background_image) && $url !== "none" + && ($repeat = $style->background_repeat) && $repeat !== "repeat" && $repeat !== "repeat-y" + ) { + $style->background_image = "none"; + } + + // Add $frame and all following siblings to the new split node + $iter = $frame; + while ($iter) { + $frame = $iter; + $iter = $iter->get_next_sibling(); + $frame->reset(); + $split->append_child($frame); + } + + $page_breaks = array("always", "left", "right"); + $frame_style = $frame->get_style(); + if( $force_pagebreak || + in_array($frame_style->page_break_before, $page_breaks) || + in_array($frame_style->page_break_after, $page_breaks) ) { + + $this->get_parent()->split($split, true); + } + } + +} diff --git a/library/vendor/dompdf/include/inline_frame_reflower.cls.php b/library/vendor/dompdf/include/inline_frame_reflower.cls.php new file mode 100644 index 000000000..049b8e586 --- /dev/null +++ b/library/vendor/dompdf/include/inline_frame_reflower.cls.php @@ -0,0 +1,66 @@ + + * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + */ + +/** + * Reflows inline frames + * + * @access private + * @package dompdf + */ +class Inline_Frame_Reflower extends Frame_Reflower { + + function __construct(Frame $frame) { parent::__construct($frame); } + + //........................................................................ + + function reflow(Block_Frame_Decorator $block = null) { + $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; + + $style = $frame->get_style(); + + // Generated content + $this->_set_content(); + + $frame->position(); + + $cb = $frame->get_containing_block(); + + // Add our margin, padding & border to the first and last children + if ( ($f = $frame->get_first_child()) && $f instanceof Text_Frame_Decorator ) { + $f_style = $f->get_style(); + $f_style->margin_left = $style->margin_left; + $f_style->padding_left = $style->padding_left; + $f_style->border_left = $style->border_left; + } + + if ( ($l = $frame->get_last_child()) && $l instanceof Text_Frame_Decorator ) { + $l_style = $l->get_style(); + $l_style->margin_right = $style->margin_right; + $l_style->padding_right = $style->padding_right; + $l_style->border_right = $style->border_right; + } + + if ( $block ) { + $block->add_frame_to_line($this->_frame); + } + + // 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); + } + } +} diff --git a/library/vendor/dompdf/include/inline_positioner.cls.php b/library/vendor/dompdf/include/inline_positioner.cls.php new file mode 100644 index 000000000..1694ce8e2 --- /dev/null +++ b/library/vendor/dompdf/include/inline_positioner.cls.php @@ -0,0 +1,70 @@ + + * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + */ + +/** + * Positions inline frames + * + * @access private + * @package dompdf + */ +class Inline_Positioner extends Positioner { + + function __construct(Frame_Decorator $frame) { parent::__construct($frame); } + + //........................................................................ + + function position() { + /** + * Find our nearest block level parent and access its lines property. + * @var Block_Frame_Decorator + */ + $p = $this->_frame->find_block_parent(); + + // Debugging code: + +// pre_r("\nPositioning:"); +// pre_r("Me: " . $this->_frame->get_node()->nodeName . " (" . spl_object_hash($this->_frame->get_node()) . ")"); +// pre_r("Parent: " . $p->get_node()->nodeName . " (" . spl_object_hash($p->get_node()) . ")"); + + // End debugging + + if ( !$p ) + throw new DOMPDF_Exception("No block-level parent found. Not good."); + + $f = $this->_frame; + + $cb = $f->get_containing_block(); + $line = $p->get_current_line_box(); + + // Skip the page break if in a fixed position element + $is_fixed = false; + while($f = $f->get_parent()) { + if($f->get_style()->position === "fixed") { + $is_fixed = true; + break; + } + } + + $f = $this->_frame; + + if ( !$is_fixed && $f->get_parent() && + $f->get_parent() instanceof Inline_Frame_Decorator && + $f->is_text_node() ) { + + $min_max = $f->get_reflower()->get_min_max_width(); + + // If the frame doesn't fit in the current line, a line break occurs + if ( $min_max["min"] > ($cb["w"] - $line->left - $line->w - $line->right) ) { + $p->add_line(); + } + } + + $f->set_position($cb["x"] + $line->w, $line->y); + + } +} diff --git a/library/vendor/dompdf/include/inline_renderer.cls.php b/library/vendor/dompdf/include/inline_renderer.cls.php new file mode 100644 index 000000000..7a8ff51c3 --- /dev/null +++ b/library/vendor/dompdf/include/inline_renderer.cls.php @@ -0,0 +1,190 @@ + + * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + */ + +/** + * Renders inline frames + * + * @access private + * @package dompdf + */ +class Inline_Renderer extends Abstract_Renderer { + + //........................................................................ + + function render(Frame $frame) { + $style = $frame->get_style(); + + if ( !$frame->get_first_child() ) + return; // No children, no service + + // Draw the left border if applicable + $bp = $style->get_border_properties(); + $widths = array($style->length_in_pt($bp["top"]["width"]), + $style->length_in_pt($bp["right"]["width"]), + $style->length_in_pt($bp["bottom"]["width"]), + $style->length_in_pt($bp["left"]["width"])); + + // Draw the background & border behind each child. To do this we need + // to figure out just how much space each child takes: + list($x, $y) = $frame->get_first_child()->get_position(); + $w = null; + $h = 0; +// $x += $widths[3]; +// $y += $widths[0]; + + $this->_set_opacity( $frame->get_opacity( $style->opacity ) ); + + $first_row = true; + + foreach ($frame->get_children() as $child) { + list($child_x, $child_y, $child_w, $child_h) = $child->get_padding_box(); + + if ( !is_null($w) && $child_x < $x + $w ) { + //This branch seems to be supposed to being called on the first part + //of an inline html element, and the part after the if clause for the + //parts after a line break. + //But because $w initially mostly is 0, and gets updated only on the next + //round, this seem to be never executed and the common close always. + + // The next child is on another line. Draw the background & + // borders on this line. + + // Background: + if ( ($bg = $style->background_color) !== "transparent" ) + $this->_canvas->filled_rectangle( $x, $y, $w, $h, $bg); + + if ( ($url = $style->background_image) && $url !== "none" ) { + $this->_background_image($url, $x, $y, $w, $h, $style); + } + + // If this is the first row, draw the left border + if ( $first_row ) { + + if ( $bp["left"]["style"] !== "none" && $bp["left"]["color"] !== "transparent" && $bp["left"]["width"] > 0 ) { + $method = "_border_" . $bp["left"]["style"]; + $this->$method($x, $y, $h + $widths[0] + $widths[2], $bp["left"]["color"], $widths, "left"); + } + $first_row = false; + } + + // Draw the top & bottom borders + if ( $bp["top"]["style"] !== "none" && $bp["top"]["color"] !== "transparent" && $bp["top"]["width"] > 0 ) { + $method = "_border_" . $bp["top"]["style"]; + $this->$method($x, $y, $w + $widths[1] + $widths[3], $bp["top"]["color"], $widths, "top"); + } + + if ( $bp["bottom"]["style"] !== "none" && $bp["bottom"]["color"] !== "transparent" && $bp["bottom"]["width"] > 0 ) { + $method = "_border_" . $bp["bottom"]["style"]; + $this->$method($x, $y + $h + $widths[0] + $widths[2], $w + $widths[1] + $widths[3], $bp["bottom"]["color"], $widths, "bottom"); + } + + // Handle anchors & links + $link_node = null; + if ( $frame->get_node()->nodeName === "a" ) { + $link_node = $frame->get_node(); + } + else if ( $frame->get_parent()->get_node()->nodeName === "a" ){ + $link_node = $frame->get_parent()->get_node(); + } + + if ( $link_node && $href = $link_node->getAttribute("href") ) { + $this->_canvas->add_link($href, $x, $y, $w, $h); + } + + $x = $child_x; + $y = $child_y; + $w = $child_w; + $h = $child_h; + continue; + } + + if ( is_null($w) ) + $w = $child_w; + else + $w += $child_w; + + $h = max($h, $child_h); + + if (DEBUG_LAYOUT && DEBUG_LAYOUT_INLINE) { + $this->_debug_layout($child->get_border_box(), "blue"); + if (DEBUG_LAYOUT_PADDINGBOX) { + $this->_debug_layout($child->get_padding_box(), "blue", array(0.5, 0.5)); + } + } + } + + + // Handle the last child + if ( ($bg = $style->background_color) !== "transparent" ) + $this->_canvas->filled_rectangle( $x + $widths[3], $y + $widths[0], $w, $h, $bg); + + //On continuation lines (after line break) of inline elements, the style got copied. + //But a non repeatable background image should not be repeated on the next line. + //But removing the background image above has never an effect, and removing it below + //removes it always, even on the initial line. + //Need to handle it elsewhere, e.g. on certain ...clone()... usages. + // Repeat not given: default is Style::__construct + // ... && (!($repeat = $style->background_repeat) || $repeat === "repeat" ... + //different position? $this->_background_image($url, $x, $y, $w, $h, $style); + if ( ($url = $style->background_image) && $url !== "none" ) + $this->_background_image($url, $x + $widths[3], $y + $widths[0], $w, $h, $style); + + // Add the border widths + $w += $widths[1] + $widths[3]; + $h += $widths[0] + $widths[2]; + + // make sure the border and background start inside the left margin + $left_margin = $style->length_in_pt($style->margin_left); + $x += $left_margin; + + // If this is the first row, draw the left border too + if ( $first_row && $bp["left"]["style"] !== "none" && $bp["left"]["color"] !== "transparent" && $widths[3] > 0 ) { + $method = "_border_" . $bp["left"]["style"]; + $this->$method($x, $y, $h, $bp["left"]["color"], $widths, "left"); + } + + // Draw the top & bottom borders + if ( $bp["top"]["style"] !== "none" && $bp["top"]["color"] !== "transparent" && $widths[0] > 0 ) { + $method = "_border_" . $bp["top"]["style"]; + $this->$method($x, $y, $w, $bp["top"]["color"], $widths, "top"); + } + + if ( $bp["bottom"]["style"] !== "none" && $bp["bottom"]["color"] !== "transparent" && $widths[2] > 0 ) { + $method = "_border_" . $bp["bottom"]["style"]; + $this->$method($x, $y + $h, $w, $bp["bottom"]["color"], $widths, "bottom"); + } + + // pre_var_dump(get_class($frame->get_next_sibling())); + // $last_row = get_class($frame->get_next_sibling()) !== 'Inline_Frame_Decorator'; + // Draw the right border if this is the last row + if ( $bp["right"]["style"] !== "none" && $bp["right"]["color"] !== "transparent" && $widths[1] > 0 ) { + $method = "_border_" . $bp["right"]["style"]; + $this->$method($x + $w, $y, $h, $bp["right"]["color"], $widths, "right"); + } + + // Only two levels of links frames + $link_node = null; + if ( $frame->get_node()->nodeName === "a" ) { + $link_node = $frame->get_node(); + + if ( ($name = $link_node->getAttribute("name")) || ($name = $link_node->getAttribute("id")) ) { + $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 ( $link_node ) { + if ( $href = $link_node->getAttribute("href") ) + $this->_canvas->add_link($href, $x, $y, $w, $h); + } + } +} diff --git a/library/vendor/dompdf/include/javascript_embedder.cls.php b/library/vendor/dompdf/include/javascript_embedder.cls.php new file mode 100644 index 000000000..92c244b2d --- /dev/null +++ b/library/vendor/dompdf/include/javascript_embedder.cls.php @@ -0,0 +1,37 @@ + + * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + */ + +/** + * Embeds Javascript into the PDF document + * + * @access private + * @package dompdf + */ +class Javascript_Embedder { + + /** + * @var DOMPDF + */ + protected $_dompdf; + + function __construct(DOMPDF $dompdf) { + $this->_dompdf = $dompdf; + } + + function insert($script) { + $this->_dompdf->get_canvas()->javascript($script); + } + + function render(Frame $frame) { + if ( !$this->_dompdf->get_option("enable_javascript") ) { + return; + } + + $this->insert($frame->get_node()->nodeValue); + } +} diff --git a/library/vendor/dompdf/include/line_box.cls.php b/library/vendor/dompdf/include/line_box.cls.php new file mode 100644 index 000000000..352359729 --- /dev/null +++ b/library/vendor/dompdf/include/line_box.cls.php @@ -0,0 +1,252 @@ + + * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + */ + +/** + * The line box class + * + * This class represents a line box + * http://www.w3.org/TR/CSS2/visuren.html#line-box + * + * @access protected + * @package dompdf + */ +class Line_Box { + + /** + * @var Block_Frame_Decorator + */ + protected $_block_frame; + + /** + * @var Frame[] + */ + protected $_frames = array(); + + /** + * @var integer + */ + 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 Frame + */ + public $tallest_frame = null; + + /** + * @var bool[] + */ + public $floating_blocks = array(); + + /** + * @var bool + */ + public $br = false; + + /** + * Class constructor + * + * @param Block_Frame_Decorator $frame the Block_Frame_Decorator containing this line + */ + function __construct(Block_Frame_Decorator $frame, $y = 0) { + $this->_block_frame = $frame; + $this->_frames = array(); + $this->y = $y; + + $this->get_float_offsets(); + } + + /** + * Returns the floating elements inside the first floating parent + * + * @param Page_Frame_Decorator $root + * + * @return Frame[] + */ + function get_floats_inside(Page_Frame_Decorator $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 = array(); + + foreach ($floating_frames as $_floating) { + $p = $_floating->get_parent(); + + while (($p = $p->get_parent()) && $p !== $parent); + + if ( $p ) { + $childs[] = $p; + } + } + + return $childs; + } + + function get_float_offsets() { + $enable_css_float = $this->_block_frame->get_dompdf()->get_option("enable_css_float"); + if ( !$enable_css_float ) { + return; + } + + static $anti_infinite_loop = 500; // 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; + } + + $floating_frames = $this->get_floats_inside($root); + + foreach ( $floating_frames as $child_key => $floating_frame ) { + $id = $floating_frame->get_id(); + + if ( isset($this->floating_blocks[$id]) ) { + continue; + } + + $floating_style = $floating_frame->get_style(); + $float = $floating_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" ) + $this->left += $floating_width; + else + $this->right += $floating_width; + + $this->floating_blocks[$id] = true; + } + + // else, the floating element won't shift anymore + else { + $root->remove_floating_frame($child_key); + } + } + } + + /** + * @return float + */ + function get_width(){ + return $this->left + $this->w + $this->right; + } + + /** + * @return Block_Frame_Decorator + */ + function get_block_frame() { + return $this->_block_frame; + } + + /** + * @return Frame[] + */ + function &get_frames() { + return $this->_frames; + } + + /** + * @param Frame $frame + */ + function add_frame(Frame $frame) { + $this->_frames[] = $frame; + } + + function __toString(){ + $props = array("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; + } + /*function __get($prop) { + if (!isset($this->{"_$prop"})) return; + return $this->{"_$prop"}; + }*/ +} + +/* +class LineBoxList implements Iterator { + private $_p = 0; + private $_lines = array(); + +} +*/ diff --git a/library/vendor/dompdf/include/list_bullet_frame_decorator.cls.php b/library/vendor/dompdf/include/list_bullet_frame_decorator.cls.php new file mode 100644 index 000000000..a661dc050 --- /dev/null +++ b/library/vendor/dompdf/include/list_bullet_frame_decorator.cls.php @@ -0,0 +1,65 @@ + + * @author Helmut Tischer + * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + */ + +/** + * Decorates frames for list bullet rendering + * + * @access private + * @package dompdf + */ +class List_Bullet_Frame_Decorator extends Frame_Decorator { + + const BULLET_PADDING = 1; // Distance from bullet to text in pt + // As fraction of font size (including descent). See also DECO_THICKNESS. + const BULLET_THICKNESS = 0.04; // Thickness of bullet outline. Screen: 0.08, print: better less, e.g. 0.04 + const BULLET_DESCENT = 0.3; //descent of font below baseline. Todo: Guessed for now. + const BULLET_SIZE = 0.35; // bullet diameter. For now 0.5 of font_size without descent. + + static $BULLET_TYPES = array("disc", "circle", "square"); + + //........................................................................ + + function __construct(Frame $frame, DOMPDF $dompdf) { + parent::__construct($frame, $dompdf); + } + + function get_margin_width() { + $style = $this->_frame->get_style(); + + // Small hack to prevent extra indenting of list text on list_style_position === "inside" + // and on suppressed bullet + if ( $style->list_style_position === "outside" || + $style->list_style_type === "none" ) { + return 0; + } + + return $style->get_font_size() * self::BULLET_SIZE + 2 * self::BULLET_PADDING; + } + + //hits only on "inset" lists items, to increase height of box + function get_margin_height() { + $style = $this->_frame->get_style(); + + if ( $style->list_style_type === "none" ) { + return 0; + } + + return $style->get_font_size() * self::BULLET_SIZE + 2 * self::BULLET_PADDING; + } + + function get_width() { + return $this->get_margin_height(); + } + + function get_height() { + return $this->get_margin_height(); + } + + //........................................................................ +} diff --git a/library/vendor/dompdf/include/list_bullet_frame_reflower.cls.php b/library/vendor/dompdf/include/list_bullet_frame_reflower.cls.php new file mode 100644 index 000000000..283056f00 --- /dev/null +++ b/library/vendor/dompdf/include/list_bullet_frame_reflower.cls.php @@ -0,0 +1,33 @@ + + * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + */ + +/** + * Reflows list bullets + * + * @access private + * @package dompdf + */ +class List_Bullet_Frame_Reflower extends Frame_Reflower { + + function __construct(Frame_Decorator $frame) { parent::__construct($frame); } + + //........................................................................ + + function reflow(Block_Frame_Decorator $block = null) { + $style = $this->_frame->get_style(); + + $style->width = $this->_frame->get_width(); + $this->_frame->position(); + + if ( $style->list_style_position === "inside" ) { + $p = $this->_frame->find_block_parent(); + $p->add_frame_to_line($this->_frame); + } + + } +} diff --git a/library/vendor/dompdf/include/list_bullet_image_frame_decorator.cls.php b/library/vendor/dompdf/include/list_bullet_image_frame_decorator.cls.php new file mode 100644 index 000000000..f27ca3d68 --- /dev/null +++ b/library/vendor/dompdf/include/list_bullet_image_frame_decorator.cls.php @@ -0,0 +1,143 @@ + + * @author Helmut Tischer + * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + */ + +/** + * Decorates frames for list bullets with custom images + * + * @access private + * @package dompdf + */ +class List_Bullet_Image_Frame_Decorator extends Frame_Decorator { + + /** + * The underlying image frame + * + * @var Image_Frame_Decorator + */ + protected $_img; + + /** + * The image's width in pixels + * + * @var int + */ + protected $_width; + + /** + * The image's height in pixels + * + * @var int + */ + protected $_height; + + /** + * Class constructor + * + * @param Frame $frame the bullet frame to decorate + * @param DOMPDF $dompdf the document's dompdf object + */ + 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_Decorator($frame, $dompdf); + parent::__construct($this->_img, $dompdf); + list($width, $height) = dompdf_getimagesize($this->_img->get_image_url()); + + // Resample the bullet image to be consistent with 'auto' sized images + // See also Image_Frame_Reflower::get_min_max_width + // Tested php ver: value measured in px, suffix "px" not in value: rtrim unnecessary. + $dpi = $this->_dompdf->get_option("dpi"); + $this->_width = ((float)rtrim($width, "px") * 72) / $dpi; + $this->_height = ((float)rtrim($height, "px") * 72) / $dpi; + + //If an image is taller as the containing block/box, the box should be extended. + //Neighbour elements are overwriting the overlapping image areas. + //Todo: Where can the box size be extended? + //Code below has no effect. + //See block_frame_reflower _calculate_restricted_height + //See generated_frame_reflower, Dompdf:render() "list-item", "-dompdf-list-bullet"S. + //Leave for now + //if ($style->min_height < $this->_height ) { + // $style->min_height = $this->_height; + //} + //$style->height = "auto"; + } + + /** + * Return the bullet's width + * + * @return int + */ + function get_width() { + //ignore image width, use same width as on predefined bullet List_Bullet_Frame_Decorator + //for proper alignment of bullet image and text. Allow image to not fitting on left border. + //This controls the distance between bullet image and text + //return $this->_width; + return $this->_frame->get_style()->get_font_size()*List_Bullet_Frame_Decorator::BULLET_SIZE + + 2 * List_Bullet_Frame_Decorator::BULLET_PADDING; + } + + /** + * Return the bullet's height + * + * @return int + */ + function get_height() { + //based on image height + return $this->_height; + } + + /** + * Override get_margin_width + * + * @return int + */ + function get_margin_width() { + //ignore image width, use same width as on predefined bullet List_Bullet_Frame_Decorator + //for proper alignment of bullet image and text. Allow image to not fitting on left border. + //This controls the extra indentation of text to make room for the bullet image. + //Here use actual image size, not predefined bullet size + //return $this->_frame->get_style()->get_font_size()*List_Bullet_Frame_Decorator::BULLET_SIZE + + // 2 * List_Bullet_Frame_Decorator::BULLET_PADDING; + + // Small hack to prevent indenting of list text + // Image Might not exist, then position like on list_bullet_frame_decorator fallback to none. + if ( $this->_frame->get_style()->list_style_position === "outside" || + $this->_width == 0) + return 0; + //This aligns the "inside" image position with the text. + //The text starts to the right of the image. + //Between the image and the text there is an added margin of image width. + //Where this comes from is unknown. + //The corresponding List_Bullet_Frame_Decorator sets a smaller margin. bullet size? + return $this->_width + 2 * List_Bullet_Frame_Decorator::BULLET_PADDING; + } + + /** + * Override get_margin_height() + * + * @return int + */ + function get_margin_height() { + //Hits only on "inset" lists items, to increase height of box + //based on image height + return $this->_height + 2 * List_Bullet_Frame_Decorator::BULLET_PADDING; + } + + /** + * Return image url + * + * @return string + */ + function get_image_url() { + return $this->_img->get_image_url(); + } + +} diff --git a/library/vendor/dompdf/include/list_bullet_positioner.cls.php b/library/vendor/dompdf/include/list_bullet_positioner.cls.php new file mode 100644 index 000000000..7e89ae471 --- /dev/null +++ b/library/vendor/dompdf/include/list_bullet_positioner.cls.php @@ -0,0 +1,73 @@ + + * @author Helmut Tischer + * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + */ + +/** + * Positions list bullets + * + * @access private + * @package dompdf + */ +class List_Bullet_Positioner extends Positioner { + + function __construct(Frame_Decorator $frame) { parent::__construct($frame); } + + //........................................................................ + + function position() { + + // Bullets & friends are positioned an absolute distance to the left of + // the content edge of their parent element + $cb = $this->_frame->get_containing_block(); + + // Note: this differs from most frames in that we must position + // ourselves after determining our width + $x = $cb["x"] - $this->_frame->get_width(); + + $p = $this->_frame->find_block_parent(); + + $y = $p->get_current_line_box()->y; + + // This is a bit of a hack... + $n = $this->_frame->get_next_sibling(); + if ( $n ) { + $style = $n->get_style(); + $line_height = $style->length_in_pt($style->line_height, $style->get_font_size()); + $offset = $style->length_in_pt($line_height, $n->get_containing_block("h")) - $this->_frame->get_height(); + $y += $offset / 2; + } + + // Now the position is the left top of the block which should be marked with the bullet. + // We tried to find out the y of the start of the first text character within the block. + // But the top margin/padding does not fit, neither from this nor from the next sibling + // The "bit of a hack" above does not work also. + + // Instead let's position the bullet vertically centered to the block which should be marked. + // But for get_next_sibling() the get_containing_block is all zero, and for find_block_parent() + // the get_containing_block is paper width and the entire list as height. + + // if ($p) { + // //$cb = $n->get_containing_block(); + // $cb = $p->get_containing_block(); + // $y += $cb["h"]/2; + // print 'cb:'.$cb["x"].':'.$cb["y"].':'.$cb["w"].':'.$cb["h"].':'; + // } + + // Todo: + // For now give up on the above. Use Guesswork with font y-pos in the middle of the line spacing + + /*$style = $p->get_style(); + $font_size = $style->get_font_size(); + $line_height = $style->length_in_pt($style->line_height, $font_size); + $y += ($line_height - $font_size) / 2; */ + + //Position is x-end y-top of character position of the bullet. + $this->_frame->set_position($x, $y); + + } +} diff --git a/library/vendor/dompdf/include/list_bullet_renderer.cls.php b/library/vendor/dompdf/include/list_bullet_renderer.cls.php new file mode 100644 index 000000000..6b984764f --- /dev/null +++ b/library/vendor/dompdf/include/list_bullet_renderer.cls.php @@ -0,0 +1,236 @@ + + * @author Helmut Tischer + * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + */ + +/** + * Renders list bullets + * + * @access private + * @package dompdf + */ +class List_Bullet_Renderer extends Abstract_Renderer { + static function get_counter_chars($type) { + static $cache = array(); + + 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 .= unichr($i+944); + } + break; + } + + if ( $uppercase ) { + $text = strtoupper($text); + } + + return $cache[$type] = "$text."; + } + + /** + * @param integer $n + * @param string $type + * @param integer $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 % 26) + ord('a') - 1); + break; + + case "upper-roman": + case "I": + $uppercase = true; + case "lower-roman": + case "i": + $text = dec2roman($n); + break; + + case "lower-greek": + $text = unichr($n + 944); + break; + } + + if ( $uppercase ) { + $text = strtoupper($text); + } + + return "$text."; + } + + function render(Frame $frame) { + $style = $frame->get_style(); + $font_size = $style->get_font_size(); + $line_height = $style->length_in_pt($style->line_height, $frame->get_containing_block("w")); + + $this->_set_opacity( $frame->get_opacity( $style->opacity ) ); + + $li = $frame->get_parent(); + + // Don't render bullets twice if if was split + if ($li->_splitted) { + return; + } + + // Handle list-style-image + // If list style image is requested but missing, fall back to predefined types + if ( $style->list_style_image !== "none" && + !Image_Cache::is_broken($img = $frame->get_image_url())) { + + list($x,$y) = $frame->get_position(); + + //For expected size and aspect, instead of box size, use image natural size scaled to DPI. + // Resample the bullet image to be consistent with 'auto' sized images + // See also Image_Frame_Reflower::get_min_max_width + // Tested php ver: value measured in px, suffix "px" not in value: rtrim unnecessary. + //$w = $frame->get_width(); + //$h = $frame->get_height(); + list($width, $height) = dompdf_getimagesize($img); + $dpi = $this->_dompdf->get_option("dpi"); + $w = ((float)rtrim($width, "px") * 72) / $dpi; + $h = ((float)rtrim($height, "px") * 72) / $dpi; + + $x -= $w; + $y -= ($line_height - $font_size)/2; //Reverse hinting of list_bullet_positioner + + $this->_canvas->image( $img, $x, $y, $w, $h); + + } else { + + $bullet_style = $style->list_style_type; + + $fill = false; + + switch ($bullet_style) { + + default: + case "disc": + $fill = true; + + case "circle": + list($x,$y) = $frame->get_position(); + $r = ($font_size*(List_Bullet_Frame_Decorator::BULLET_SIZE /*-List_Bullet_Frame_Decorator::BULLET_THICKNESS*/ ))/2; + $x -= $font_size*(List_Bullet_Frame_Decorator::BULLET_SIZE/2); + $y += ($font_size*(1-List_Bullet_Frame_Decorator::BULLET_DESCENT))/2; + $o = $font_size*List_Bullet_Frame_Decorator::BULLET_THICKNESS; + $this->_canvas->circle($x, $y, $r, $style->color, $o, null, $fill); + break; + + case "square": + list($x, $y) = $frame->get_position(); + $w = $font_size*List_Bullet_Frame_Decorator::BULLET_SIZE; + $x -= $w; + $y += ($font_size*(1-List_Bullet_Frame_Decorator::BULLET_DESCENT-List_Bullet_Frame_Decorator::BULLET_SIZE))/2; + $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; + } + + $spacing = 0; + $font_family = $style->font_family; + + $line = $li->get_containing_line(); + list($x, $y) = array($frame->get_position("x"), $line->y); + + $x -= Font_Metrics::get_text_width($text, $font_family, $font_size, $spacing); + + // Take line-height into account + $line_height = $style->line_height; + $y += ($line_height - $font_size) / 4; // FIXME I thought it should be 2, but 4 gives better results + + $this->_canvas->text($x, $y, $text, + $font_family, $font_size, + $style->color, $spacing); + + case "none": + break; + } + } + } +} diff --git a/library/vendor/dompdf/include/null_frame_decorator.cls.php b/library/vendor/dompdf/include/null_frame_decorator.cls.php new file mode 100644 index 000000000..15f806d0d --- /dev/null +++ b/library/vendor/dompdf/include/null_frame_decorator.cls.php @@ -0,0 +1,26 @@ + + * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + */ + +/** + * Dummy decorator + * + * @access private + * @package dompdf + */ +class Null_Frame_Decorator extends Frame_Decorator { + + 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; + } + +} diff --git a/library/vendor/dompdf/include/null_frame_reflower.cls.php b/library/vendor/dompdf/include/null_frame_reflower.cls.php new file mode 100644 index 000000000..389f1479d --- /dev/null +++ b/library/vendor/dompdf/include/null_frame_reflower.cls.php @@ -0,0 +1,21 @@ + + * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + */ + +/** + * Dummy reflower + * + * @access private + * @package dompdf + */ +class Null_Frame_Reflower extends Frame_Reflower { + + function __construct(Frame $frame) { parent::__construct($frame); } + + function reflow(Block_Frame_Decorator $block = null) { return; } + +} diff --git a/library/vendor/dompdf/include/null_positioner.cls.php b/library/vendor/dompdf/include/null_positioner.cls.php new file mode 100644 index 000000000..97d4986dc --- /dev/null +++ b/library/vendor/dompdf/include/null_positioner.cls.php @@ -0,0 +1,23 @@ + + * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + */ + +/** + * Dummy positioner + * + * @access private + * @package dompdf + */ +class Null_Positioner extends Positioner { + + function __construct(Frame_Decorator $frame) { + parent::__construct($frame); + } + + function position() { return; } + +} diff --git a/library/vendor/dompdf/include/page_cache.cls.php b/library/vendor/dompdf/include/page_cache.cls.php new file mode 100644 index 000000000..652da160a --- /dev/null +++ b/library/vendor/dompdf/include/page_cache.cls.php @@ -0,0 +1,126 @@ + + * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + */ + +/** + * Caches individual rendered PDF pages + * + * Not totally implemented yet. Use at your own risk ;) + * + * @access private + * @package dompdf + * @static + */ +class Page_Cache { + + const DB_USER = "dompdf_page_cache"; + const DB_PASS = "some meaningful password"; + const DB_NAME = "dompdf_page_cache"; + + static private $__connection = null; + + static function init() { + if ( is_null(self::$__connection) ) { + $con_str = "host=" . DB_HOST . + " dbname=" . self::DB_NAME . + " user=" . self::DB_USER . + " password=" . self::DB_PASS; + + if ( !self::$__connection = pg_connect($con_str) ) + throw new Exception("Database connection failed."); + } + } + + function __construct() { throw new Exception("Can not create instance of Page_Class. Class is static."); } + + private static function __query($sql) { + if ( !($res = pg_query(self::$__connection, $sql)) ) + throw new Exception(pg_last_error(self::$__connection)); + return $res; + } + + static function store_page($id, $page_num, $data) { + $where = "WHERE id='" . pg_escape_string($id) . "' AND ". + "page_num=". pg_escape_string($page_num); + + $res = self::__query("SELECT timestamp FROM page_cache ". $where); + + $row = pg_fetch_assoc($res); + + if ( $row ) + self::__query("UPDATE page_cache SET data='" . pg_escape_string($data) . "' " . $where); + else + self::__query("INSERT INTO page_cache (id, page_num, data) VALUES ('" . pg_escape_string($id) . "', ". + pg_escape_string($page_num) . ", ". + "'". pg_escape_string($data) . "')"); + + } + + static function store_fonts($id, $fonts) { + self::__query("BEGIN"); + // Update the font information + self::__query("DELETE FROM page_fonts WHERE id='" . pg_escape_string($id) . "'"); + + foreach (array_keys($fonts) as $font) + self::__query("INSERT INTO page_fonts (id, font_name) VALUES ('" . + pg_escape_string($id) . "', '" . pg_escape_string($font) . "')"); + self::__query("COMMIT"); + } + +// static function retrieve_page($id, $page_num) { + +// $res = self::__query("SELECT data FROM page_cache WHERE id='" . pg_escape_string($id) . "' AND ". +// "page_num=". pg_escape_string($page_num)); + +// $row = pg_fetch_assoc($res); + +// return pg_unescape_bytea($row["data"]); + +// } + + static function get_page_timestamp($id, $page_num) { + $res = self::__query("SELECT timestamp FROM page_cache WHERE id='" . pg_escape_string($id) . "' AND ". + "page_num=". pg_escape_string($page_num)); + + $row = pg_fetch_assoc($res); + + return $row["timestamp"]; + + } + + // Adds the cached document referenced by $id to the provided pdf + static function insert_cached_document(CPDF_Adapter $pdf, $id, $new_page = true) { + $res = self::__query("SELECT font_name FROM page_fonts WHERE id='" . pg_escape_string($id) . "'"); + + // Ensure that the fonts needed by the cached document are loaded into + // the pdf + while ($row = pg_fetch_assoc($res)) + $pdf->get_cpdf()->selectFont($row["font_name"]); + + $res = self::__query("SELECT data FROM page_cache WHERE id='" . pg_escape_string($id) . "'"); + + if ( $new_page ) + $pdf->new_page(); + + $first = true; + while ($row = pg_fetch_assoc($res)) { + + if ( !$first ) + $pdf->new_page(); + else + $first = false; + + $page = $pdf->reopen_serialized_object($row["data"]); + //$pdf->close_object(); + $pdf->add_object($page, "add"); + + } + + } +} + +Page_Cache::init(); diff --git a/library/vendor/dompdf/include/page_frame_decorator.cls.php b/library/vendor/dompdf/include/page_frame_decorator.cls.php new file mode 100644 index 000000000..f089d0075 --- /dev/null +++ b/library/vendor/dompdf/include/page_frame_decorator.cls.php @@ -0,0 +1,592 @@ + + * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + */ + +/** + * Decorates frames for page layout + * + * @access private + * @package dompdf + */ +class Page_Frame_Decorator extends Frame_Decorator { + + /** + * y value of bottom page margin + * + * @var float + */ + protected $_bottom_page_margin; + + /** + * 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 = array(); + + //........................................................................ + + /** + * 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_margin = 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; + } + + /** + * Set the frame's containing block. Overridden to set $this->_bottom_page_margin. + * + * @param float $x + * @param float $y + * @param float $w + * @param float $h + */ + function set_containing_block($x = null, $y = null, $w = null, $h = null) { + parent::set_containing_block($x,$y,$w,$h); + //$w = $this->get_containing_block("w"); + if ( isset($h) ) + $this->_bottom_page_margin = $h; // - $this->_frame->get_style()->length_in_pt($this->_frame->get_style()->margin_bottom, $w); + } + + /** + * 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 = array(); + $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 preceeding frame's + * page_break_after property. + * + * @link http://www.w3.org/TR/CSS21/page.html#forced + * + * @param Frame $frame the frame to check + * @return bool true if a page break occured + */ + function check_forced_page_break(Frame $frame) { + + // Skip check if page is already split + if ( $this->_page_full ) + return null; + + $block_types = array("block", "list-item", "table", "inline"); + $page_breaks = array("always", "left", "right"); + + $style = $frame->get_style(); + + if ( !in_array($style->display, $block_types) ) + return false; + + // Find the previous block-level sibling + $prev = $frame->get_prev_sibling(); + + while ( $prev && !in_array($prev->get_style()->display, $block_types) ) + $prev = $prev->get_prev_sibling(); + + + if ( in_array($style->page_break_before, $page_breaks) ) { + + // Prevent cascading splits + $frame->split(null, true); + // We have to grab the style again here because split() resets + // $frame->style to the frame's orignal style. + $frame->get_style()->page_break_before = "auto"; + $this->_page_full = true; + + return true; + } + + if ( $prev && in_array($prev->get_style()->page_break_after, $page_breaks) ) { + // Prevent cascading splits + $frame->split(null, true); + $prev->get_style()->page_break_after = "auto"; + $this->_page_full = true; + return true; + } + + if( $prev && $prev->get_last_child() && $frame->get_node()->nodeName != "body" ) { + $prev_last_child = $prev->get_last_child(); + if ( in_array($prev_last_child->get_style()->page_break_after, $page_breaks) ) { + $frame->split(null, true); + $prev_last_child->get_style()->page_break_after = "auto"; + $this->_page_full = true; + return true; + } + } + + + return false; + } + + /** + * 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 a page + * break occurs here, the used values of the relevant + * 'margin-top' and 'margin-bottom' properties are set to '0'. + * 2. Between line boxes inside a block box. + * + * 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 the + * nearest 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) is allowed only if + * the 'page-break-inside' property is 'auto'. + * + * If the above doesn't provide enough break points to keep + * content from overflowing the page boxes, then rules B and D are + * dropped in order to find additional breakpoints. + * + * If that still does not lead to sufficient break points, rules A + * and C are dropped as well, to find still more break points. + * + * We will also allow breaks between table rows. However, when + * splitting a table, the table headers should carry over to the + * next page (but they don't yet). + * + * @param Frame $frame the frame to check + * @return bool true if a break is allowed, false otherwise + */ + protected function _page_break_allowed(Frame $frame) { + + $block_types = array("block", "list-item", "table", "-dompdf-image"); + dompdf_debug("page-break", "_page_break_allowed(" . $frame->get_node()->nodeName. ")"); + $display = $frame->get_style()->display; + + // Block Frames (1): + if ( in_array($display, $block_types) ) { + + // Avoid breaks within table-cells + if ( $this->_in_table ) { + dompdf_debug("page-break", "In table: " . $this->_in_table); + return false; + } + + // Rules A & B + + if ( $frame->get_style()->page_break_before === "avoid" ) { + dompdf_debug("page-break", "before: avoid"); + return false; + } + + // Find the preceeding block-level sibling + $prev = $frame->get_prev_sibling(); + while ( $prev && !in_array($prev->get_style()->display, $block_types) ) + $prev = $prev->get_prev_sibling(); + + // Does the previous element allow a page break after? + if ( $prev && $prev->get_style()->page_break_after === "avoid" ) { + dompdf_debug("page-break", "after: avoid"); + return false; + } + + // If both $prev & $frame have the same parent, check the parent's + // page_break_inside property. + $parent = $frame->get_parent(); + if ( $prev && $parent && $parent->get_style()->page_break_inside === "avoid" ) { + dompdf_debug("page-break", "parent inside: avoid"); + return false; + } + + // 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 + dompdf_debug("page-break", "Body's first child."); + return false; + } + + // If the frame is the first block-level frame, use the value from + // $frame's parent instead. + if ( !$prev && $parent ) + return $this->_page_break_allowed( $parent ); + + dompdf_debug("page-break", "block: break allowed"); + return true; + + } + + // Inline frames (2): + else if ( in_array($display, Style::$INLINE_TYPES) ) { + + // Avoid breaks within table-cells + if ( $this->_in_table ) { + dompdf_debug("page-break", "In table: " . $this->_in_table); + return false; + } + + // Rule C + $block_parent = $frame->find_block_parent(); + if ( count($block_parent->get_line_boxes() ) < $frame->get_style()->orphans ) { + 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" ) { + 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 + dompdf_debug("page-break", "Body's first child."); + return false; + } + + // Skip breaks on empty text nodes + if ( $frame->is_text_node() && + $frame->get_node()->nodeValue == "" ) + return false; + + dompdf_debug("page-break", "inline: break allowed"); + return true; + + // Table-rows + } else if ( $display === "table-row" ) { + + // Simply check if the parent table's page_break_inside property is + // not 'avoid' + $p = Table_Frame_Decorator::find_parent_table($frame); + + while ($p) { + if ( $p->get_style()->page_break_inside === "avoid" ) { + dompdf_debug("page-break", "parent->inside: avoid"); + return false; + } + $p = $p->find_block_parent(); + } + + // Avoid breaking after the first row of a table + if ( $p && $p->get_first_child() === $frame) { + dompdf_debug("page-break", "table: first-row"); + return false; + } + + // If this is a nested table, prevent the page from breaking + if ( $this->_in_table > 1 ) { + dompdf_debug("page-break", "table: nested table"); + return false; + } + + dompdf_debug("page-break","table-row/row-groups: break allowed"); + return true; + + } else if ( in_array($display, Table_Frame_Decorator::$ROW_GROUPS) ) { + + // Disallow breaks at row-groups: only split at row boundaries + return false; + + } else { + + dompdf_debug("page-break", "? " . $frame->get_style()->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 Frame $frame the frame to check + * @return Frame the frame following the page break + */ + function check_page_break(Frame $frame) { + // Do not split if we have already or if the frame was already + // pushed to the next page (prevents infinite loops) + if ( $this->_page_full || $frame->_already_pushed ) { + return false; + } + + // If the frame is absolute of 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(); + + // FIXME If the row is taller than the page and + // if it the first of the page, we don't break + if ( $frame->get_style()->display === "table-row" && + !$frame->get_prev_sibling() && + $margin_height > $this->get_margin_height() ) + return false; + + // Determine the frame's maximum y value + $max_y = $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 ) { + $style = $p->get_style(); + $max_y += $style->length_in_pt(array($style->margin_bottom, + $style->padding_bottom, + $style->border_bottom_width)); + $p = $p->get_parent(); + } + + + // Check if $frame flows off the page + if ( $max_y <= $this->_bottom_page_margin ) + // no: do nothing + return false; + + dompdf_debug("page-break", "check_page_break"); + dompdf_debug("page-break", "in_table: " . $this->_in_table); + + // yes: determine page break location + $iter = $frame; + $flg = false; + + $in_table = $this->_in_table; + + dompdf_debug("page-break","Starting search"); + while ( $iter ) { + // echo "\nbacktrack: " .$iter->get_node()->nodeName ." ".spl_object_hash($iter->get_node()). ""; + if ( $iter === $this ) { + dompdf_debug("page-break", "reached root."); + // We've reached the root in our search. Just split at $frame. + break; + } + + if ( $this->_page_break_allowed($iter) ) { + dompdf_debug("page-break","break allowed, splitting."); + $iter->split(null, true); + $this->_page_full = true; + $this->_in_table = $in_table; + $frame->_already_pushed = true; + return true; + } + + if ( !$flg && $next = $iter->get_last_child() ) { + dompdf_debug("page-break", "following last child."); + + if ( $next->is_table() ) + $this->_in_table++; + + $iter = $next; + continue; + } + + if ( $next = $iter->get_prev_sibling() ) { + dompdf_debug("page-break", "following prev sibling."); + + if ( $next->is_table() && !$iter->is_table() ) + $this->_in_table++; + + else if ( !$next->is_table() && $iter->is_table() ) + $this->_in_table--; + + $iter = $next; + $flg = false; + continue; + } + + if ( $next = $iter->get_parent() ) { + 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. + 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 = $iter->get_parent(); + + $iter->split(null, true); + } else { + $frame->split(null, true); + } + + $this->_page_full = true; + $frame->_already_pushed = true; + return true; + } + + //........................................................................ + + function split(Frame $frame = null, $force_pagebreak = false) { + // 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; + } + + public function remove_floating_frame($key) { + unset($this->_floating_frames[$key]); + } + + public function get_lowest_float_offset(Frame $child) { + $style = $child->get_style(); + $side = $style->clear; + $float = $style->float; + + $y = 0; + + 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()); + + if ( $float !== "none" ) { + $this->remove_floating_frame($key); + } + } + } + + return $y; + } + +} diff --git a/library/vendor/dompdf/include/page_frame_reflower.cls.php b/library/vendor/dompdf/include/page_frame_reflower.cls.php new file mode 100644 index 000000000..4223f4e85 --- /dev/null +++ b/library/vendor/dompdf/include/page_frame_reflower.cls.php @@ -0,0 +1,186 @@ + + * @author Fabien Ménager + * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + */ + +/** + * Reflows pages + * + * @access private + * @package dompdf + */ +class Page_Frame_Reflower extends Frame_Reflower { + + /** + * Cache of the callbacks array + * + * @var array + */ + private $_callbacks; + + /** + * Cache of the canvas + * + * @var Canvas + */ + private $_canvas; + + function __construct(Page_Frame_Decorator $frame) { parent::__construct($frame); } + + 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); + } + } + + //........................................................................ + + /** + * Paged layout: + * http://www.w3.org/TR/CSS21/page.html + */ + function reflow(Block_Frame_Decorator $block = null) { + $fixed_children = array(); + $prev_child = null; + $child = $this->_frame->get_first_child(); + $current_page = 0; + + while ($child) { + $this->apply_page_style($this->_frame, $current_page + 1); + + $style = $this->_frame->get_style(); + + // Pages are only concerned with margins + $cb = $this->_frame->get_containing_block(); + $left = $style->length_in_pt($style->margin_left, $cb["w"]); + $right = $style->length_in_pt($style->margin_right, $cb["w"]); + $top = $style->length_in_pt($style->margin_top, $cb["h"]); + $bottom = $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) { + $children = $child->get_children(); + foreach ($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 + $this->_frame->get_renderer()->render($child); + + // Check for end render callback + $this->_check_callbacks("end_page_render", $child); + + if ( $next_child ) { + $this->_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($event, $frame) { + if (!isset($this->_callbacks)) { + $dompdf = $this->_frame->get_dompdf(); + $this->_callbacks = $dompdf->get_callbacks(); + $this->_canvas = $dompdf->get_canvas(); + } + + if (is_array($this->_callbacks) && isset($this->_callbacks[$event])) { + $info = array(0 => $this->_canvas, "canvas" => $this->_canvas, + 1 => $frame, "frame" => $frame); + $fs = $this->_callbacks[$event]; + foreach ($fs as $f) { + if (is_callable($f)) { + if (is_array($f)) { + $f[0]->$f[1]($info); + } else { + $f($info); + } + } + } + } + } +} diff --git a/library/vendor/dompdf/include/pdflib_adapter.cls.php b/library/vendor/dompdf/include/pdflib_adapter.cls.php new file mode 100644 index 000000000..4bfe1913e --- /dev/null +++ b/library/vendor/dompdf/include/pdflib_adapter.cls.php @@ -0,0 +1,1085 @@ + + * @author Helmut Tischer + * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + */ + +/** + * PDF rendering interface + * + * PDFLib_Adapter 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_Adapter implements Canvas { + + /** + * Dimensions of paper sizes in points + * + * @var array; + */ + static public $PAPER_SIZES = array(); // Set to CPDF_Adapter::$PAPER_SIZES below. + + /** + * Whether to create PDFs in memory or on disk + * + * @var bool + */ + static $IN_MEMORY = true; + + /** + * @var DOMPDF + */ + private $_dompdf; + + /** + * Instance of PDFLib class + * + * @var PDFlib + */ + private $_pdf; + + /** + * Name of temporary file used for PDFs created on disk + * + * @var string + */ + private $_file; + + /** + * PDF width, in points + * + * @var float + */ + private $_width; + + /** + * PDF height, in points + * + * @var float + */ + private $_height; + + /** + * Last fill color used + * + * @var array + */ + private $_last_fill_color; + + /** + * Last stroke color used + * + * @var array + */ + private $_last_stroke_color; + + /** + * Cache of image handles + * + * @var array + */ + private $_imgs; + + /** + * Cache of font handles + * + * @var array + */ + private $_fonts; + + /** + * List of objects (templates) to add to multiple pages + * + * @var array + */ + private $_objs; + + /** + * Current page number + * + * @var int + */ + private $_page_number; + + /** + * Total number of pages + * + * @var int + */ + private $_page_count; + + /** + * Text to display on every page + * + * @var array + */ + private $_page_text; + + /** + * Array of pages for accesing after rendering is initially complete + * + * @var array + */ + private $_pages; + + /** + * Class constructor + * + * @param mixed $paper The size of paper to use either a string (see {@link CPDF_Adapter::$PAPER_SIZES}) or + * an array(xmin,ymin,xmax,ymax) + * @param string $orientation The orientation of the document (either 'landscape' or 'portrait') + * @param DOMPDF $dompdf + */ + function __construct($paper = "letter", $orientation = "portrait", DOMPDF $dompdf) { + if ( is_array($paper) ) { + $size = $paper; + } + else if ( isset(self::$PAPER_SIZES[mb_strtolower($paper)]) ) { + $size = self::$PAPER_SIZES[mb_strtolower($paper)]; + } + else { + $size = self::$PAPER_SIZES["letter"]; + } + + if ( mb_strtolower($orientation) === "landscape" ) { + list($size[2], $size[3]) = array($size[3], $size[2]); + } + + $this->_width = $size[2] - $size[0]; + $this->_height= $size[3] - $size[1]; + + $this->_dompdf = $dompdf; + + $this->_pdf = new PDFLib(); + + if ( defined("DOMPDF_PDFLIB_LICENSE") ) + $this->_pdf->set_parameter( "license", DOMPDF_PDFLIB_LICENSE); + + $this->_pdf->set_parameter("textformat", "utf8"); + $this->_pdf->set_parameter("fontwarning", "false"); + + $this->_pdf->set_info("Creator", "DOMPDF"); + + // 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->get_options("temp_dir"); + $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->_page_text = array(); + + $this->_imgs = array(); + $this->_fonts = array(); + $this->_objs = array(); + + // Set up font paths + $families = Font_Metrics::get_font_families(); + foreach ($families as $files) { + foreach ($files as $file) { + $face = basename($file); + $afm = null; + + // Prefer ttfs to afms + if ( file_exists("$file.ttf") ) { + $outline = "$file.ttf"; + + } else if ( file_exists("$file.TTF") ) { + $outline = "$file.TTF"; + + } else if ( file_exists("$file.pfb") ) { + $outline = "$file.pfb"; + + if ( file_exists("$file.afm") ) { + $afm = "$file.afm"; + } + + } else if ( file_exists("$file.PFB") ) { + $outline = "$file.PFB"; + if ( file_exists("$file.AFM") ) { + $afm = "$file.AFM"; + } + } else { + continue; + } + + $this->_pdf->set_parameter("FontOutline", "\{$face\}=\{$outline\}"); + + if ( !is_null($afm) ) { + $this->_pdf->set_parameter("FontAFM", "\{$face\}=\{$afm\}"); + } + } + } + } + + 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 + */ + function get_pdflib() { + return $this->_pdf; + } + + /** + * Add meta information to the PDF + * + * @param string $label label of the value (Creator, Producter, etc.) + * @param string $value the text to set + */ + function add_info($label, $value) { + $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_Adapter::close_object() + * @see PDFLib_Adapter::add_object() + * + * @return int + */ + function open_object() { + $this->_pdf->suspend_page(""); + $ret = $this->_pdf->begin_template($this->_width, $this->_height); + $this->_pdf->save(); + $this->_objs[$ret] = array("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 DOMPDF_Exception + * @return void + */ + function reopen_object($object) { + throw new DOMPDF_Exception("PDFLib does not support reopening objects."); + } + + /** + * Close the current template + * + * @see PDFLib_Adapter::open_object() + */ + function close_object() { + $this->_pdf->restore(); + $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 + */ + 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 + */ + 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,""); + } + } + + } + + function get_width() { return $this->_width; } + + function get_height() { return $this->_height; } + + function get_page_number() { return $this->_page_number; } + + function get_page_count() { return $this->_page_count; } + + function set_page_number($num) { $this->_page_number = (int)$num; } + + function set_page_count($count) { $this->_page_count = (int)$count; } + + + /** + * Sets the line style + * + * @param float $width + * @param $cap + * @param string $join + * @param array $dash + * + * @return void + */ + protected function _set_line_style($width, $cap, $join, $dash) { + + if ( count($dash) == 1 ) + $dash[] = $dash[0]; + + if ( count($dash) > 1 ) + $this->_pdf->setdashpattern("dasharray={" . implode(" ", $dash) . "}"); + else + $this->_pdf->setdash(0,0); + + switch ( $join ) { + case "miter": + $this->_pdf->setlinejoin(0); + break; + + case "round": + $this->_pdf->setlinejoin(1); + break; + + case "bevel": + $this->_pdf->setlinejoin(2); + break; + + default: + break; + } + + switch ( $cap ) { + case "butt": + $this->_pdf->setlinecap(0); + break; + + case "round": + $this->_pdf->setlinecap(1); + break; + + case "square": + $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) { + if($this->_last_stroke_color == $color) + return; + + $this->_last_stroke_color = $color; + + if (isset($color[3])) { + $type = "cmyk"; + list($c1, $c2, $c3, $c4) = array($color[0], $color[1], $color[2], $color[3]); + } + elseif (isset($color[2])) { + $type = "rgb"; + list($c1, $c2, $c3, $c4) = array($color[0], $color[1], $color[2], null); + } + else { + $type = "gray"; + list($c1, $c2, $c3, $c4) = array($color[0], $color[1], null, null); + } + + $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) { + if($this->_last_fill_color == $color) + return; + + $this->_last_fill_color = $color; + + if (isset($color[3])) { + $type = "cmyk"; + list($c1, $c2, $c3, $c4) = array($color[0], $color[1], $color[2], $color[3]); + } + elseif (isset($color[2])) { + $type = "rgb"; + list($c1, $c2, $c3, $c4) = array($color[0], $color[1], $color[2], null); + } + else { + $type = "gray"; + list($c1, $c2, $c3, $c4) = array($color[0], $color[1], null, null); + } + + $this->_pdf->setcolor("fill", $type, $c1, $c2, $c3, $c4); + } + + /** + * Sets the opacity + * + * @param $opacity + * @param $mode + */ + function set_opacity($opacity, $mode = "Normal") { + if ( $mode === "Normal" ) { + $gstate = $this->_pdf->create_gstate("opacityfill=$opacity opacitystroke=$opacity"); + $this->_pdf->set_gstate($gstate); + } + } + + function set_default_view($view, $options = array()) { + // 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->_pdf->set_parameter("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 = "") { + + // Check if the font is a native PDF font + // Embed non-native fonts + $test = strtolower(basename($font)); + if ( in_array($test, DOMPDF::$native_fonts) ) { + $font = basename($font); + + } else { + // Embed non-native fonts + $options .= " embedding=true"; + } + + if ( is_null($encoding) ) { + + // Unicode encoding is only available for the commerical + // version of PDFlib and not PDFlib-Lite + if ( defined("DOMPDF_PDFLIB_LICENSE") ) + $encoding = "unicode"; + else + $encoding = "auto"; + + } + + $key = "$font:$encoding:$options"; + + if ( isset($this->_fonts[$key]) ) + return $this->_fonts[$key]; + + else { + + $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; } + + //........................................................................ + + /** + * @param float $x1 + * @param float $y1 + * @param float $x2 + * @param float $y2 + * @param array $color + * @param float $width + * @param array $style + */ + function line($x1, $y1, $x2, $y2, $color, $width, $style = null) { + $this->_set_line_style($width, "butt", "", $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(); + } + + function arc($x1, $y1, $r1, $r2, $astart, $aend, $color, $width, $style = array()) { + $this->_set_line_style($width, "butt", "", $style); + $this->_set_stroke_color($color); + + $y1 = $this->y($y1); + + $this->_pdf->arc($x1, $y1, $r1, $astart, $aend); + $this->_pdf->stroke(); + } + + //........................................................................ + + function rectangle($x1, $y1, $w, $h, $color, $width, $style = null) { + $this->_set_stroke_color($color); + $this->_set_line_style($width, "butt", "", $style); + + $y1 = $this->y($y1) - $h; + + $this->_pdf->rect($x1, $y1, $w, $h); + $this->_pdf->stroke(); + } + + //........................................................................ + + 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(); + } + + 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(); + } + + function clipping_roundrectangle($x1, $y1, $w, $h, $rTL, $rTR, $rBR, $rBL) { + // @todo + $this->clipping_rectangle($x1, $y1, $w, $h); + } + + function clipping_end() { + $this->_pdf->restore(); + } + + function save() { + $this->_pdf->save(); + } + + function restore() { + $this->_pdf->restore(); + } + + function rotate($angle, $x, $y) { + $pdf = $this->_pdf; + $pdf->translate($x, $this->_height-$y); + $pdf->rotate(-$angle); + $pdf->translate(-$x, -$this->_height+$y); + } + + 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); + } + + 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); + } + + function translate($t_x, $t_y) { + $this->_pdf->translate($t_x, -$t_y); + } + + function transform($a, $b, $c, $d, $e, $f) { + $this->_pdf->concat($a, $b, $c, $d, $e, $f); + } + + //........................................................................ + + function polygon($points, $color, $width = null, $style = null, $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(); + } + + //........................................................................ + + function circle($x, $y, $r, $color, $width = null, $style = null, $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(); + + } + + //........................................................................ + + function image($img_url, $x, $y, $w, $h, $resolution = "normal") { + $w = (int)$w; + $h = (int)$h; + + $img_type = Image_Cache::detect_type($img_url); + $img_ext = Image_Cache::type_to_ext($img_type); + + if ( !isset($this->_imgs[$img_url]) ) { + $this->_imgs[$img_url] = $this->_pdf->load_image($img_ext, $img_url, ""); + } + + $img = $this->_imgs[$img_url]; + + $y = $this->y($y) - $h; + $this->_pdf->fit_image($img, $x, $y, 'boxsize={'."$w $h".'} fitmethod=entire'); + } + + //........................................................................ + + function text($x, $y, $text, $font, $size, $color = array(0,0,0), $word_spacing = 0, $char_spacing = 0, $angle = 0) { + $fh = $this->_load_font($font); + + $this->_pdf->setfont($fh, $size); + $this->_set_fill_color($color); + + $y = $this->y($y) - Font_Metrics::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 "); + + } + + //........................................................................ + + function javascript($code) { + if ( defined("DOMPDF_PDFLIB_LICENSE") ) { + $this->_pdf->create_action("JavaScript", $code); + } + } + + //........................................................................ + + /** + * Add a named destination (similar to ... in html) + * + * @param string $anchorname The name of the named destination + */ + function add_named_dest($anchorname) { + $this->_pdf->add_nameddest($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) { + + $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 { + + list($proto, $host, $path, $file) = explode_url($url); + + if ( $proto == "" || $proto === "file://" ) + return; // Local links are not allowed + $url = build_url($proto, $host, $path, $file); + $url = '{' . rawurldecode($url) . '}'; + + $action = $this->_pdf->create_action("URI", "url=" . $url); + $this->_pdf->create_annotation($x, $y, $x + $width, $y + $height, 'Link', "contents={$url} action={activate=$action} linewidth=0"); + } + } + + //........................................................................ + + function get_text_width($text, $font, $size, $word_spacing = 0, $letter_spacing = 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 - $num_spaces) * $letter_spacing; + } + + return $this->_pdf->stringwidth($text, $fh, $size) + $delta; + } + + //........................................................................ + + function get_font_height($font, $size) { + + $fh = $this->_load_font($font); + + $this->_pdf->setfont($fh, $size); + + $asc = $this->_pdf->get_value("ascender", $fh); + $desc = $this->_pdf->get_value("descender", $fh); + + // $desc is usually < 0, + $ratio = $this->_dompdf->get_option("font_height_ratio"); + return $size * ($asc - $desc) * $ratio; + } + + function get_font_baseline($font, $size) { + $ratio = $this->_dompdf->get_option("font_height_ratio"); + return $this->get_font_height($font, $size) / $ratio * 1.1; + } + + //........................................................................ + + /** + * 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. + * + * See {@link Style::munge_color()} for the format of the color array. + * + * @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 + * @param float $word_space word spacing adjustment + * @param float $char_space char spacing adjustment + * @param float $angle angle to write the text at, measured CW starting from the x-axis + */ + function page_text($x, $y, $text, $font, $size, $color = array(0,0,0), $word_space = 0.0, $char_space = 0.0, $angle = 0.0) { + $_t = "text"; + $this->_page_text[] = compact("_t", "x", "y", "text", "font", "size", "color", "word_space", "char_space", "angle"); + } + + //........................................................................ + + /** + * Processes a script on every page + * + * The variables $pdf, $PAGE_NUM, and $PAGE_COUNT are available. + * + * This function can be used to add page numbers to all pages + * after the first one, for example. + * + * @param string $code the script code + * @param string $type the language type for script + */ + function page_script($code, $type = "text/php") { + $_t = "script"; + $this->_page_text[] = compact("_t", "code", "type"); + } + + //........................................................................ + + 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; + + } + + //........................................................................ + + /** + * Add text to each page after rendering is complete + */ + protected function _add_page_text() { + + if ( !count($this->_page_text) ) + return; + + $this->_pdf->suspend_page(""); + + for ($p = 1; $p <= $this->_page_count; $p++) { + $this->_pdf->resume_page("pagenumber=$p"); + + foreach ($this->_page_text as $pt) { + extract($pt); + + switch ($_t) { + + case "text": + $text = str_replace(array("{PAGE_NUM}","{PAGE_COUNT}"), + array($p, $this->_page_count), $text); + $this->text($x, $y, $text, $font, $size, $color, $word_space, $char_space, $angle); + break; + + case "script": + if (!$eval) { + $eval = new PHP_Evaluator($this); + } + $eval->evaluate($code, array('PAGE_NUM' => $p, 'PAGE_COUNT' => $this->_page_count)); + break; + } + } + + $this->_pdf->suspend_page(""); + } + + $this->_pdf->resume_page("pagenumber=".$this->_page_number); + } + + //........................................................................ + + function stream($filename, $options = null) { + + // Add page text + $this->_add_page_text(); + + if ( isset($options["compress"]) && $options["compress"] != 1 ) + $this->_pdf->set_value("compress", 0); + else + $this->_pdf->set_value("compress", 6); + + $this->_close(); + + $data = ""; + + if ( self::$IN_MEMORY ) { + $data = $this->_pdf->get_buffer(); + //$size = strlen($data); + } else { + //$size = filesize($this->_file); + } + + + $filename = str_replace(array("\n","'"),"", $filename); + $attach = (isset($options["Attachment"]) && $options["Attachment"]) ? "attachment" : "inline"; + + header("Cache-Control: private"); + header("Content-type: application/pdf"); + header("Content-Disposition: $attach; filename=\"$filename\""); + + //header("Content-length: " . $size); + + if ( self::$IN_MEMORY ) + echo $data; + + else { + + // Chunked readfile() + $chunk = (1 << 21); // 2 MB + $fh = fopen($this->_file, "rb"); + if ( !$fh ) + throw new DOMPDF_Exception("Unable to load temporary PDF file: " . $this->_file); + + while ( !feof($fh) ) + echo fread($fh,$chunk); + fclose($fh); + + //debugpng + if (DEBUGPNG) print '[pdflib stream unlink '.$this->_file.']'; + if (!DEBUGKEEPTEMP) + + unlink($this->_file); + $this->_file = null; + unset($this->_file); + } + + flush(); + } + + //........................................................................ + + function output($options = null) { + + // Add page text + $this->_add_page_text(); + + if ( isset($options["compress"]) && $options["compress"] != 1 ) + $this->_pdf->set_value("compress", 0); + else + $this->_pdf->set_value("compress", 6); + + $this->_close(); + + if ( self::$IN_MEMORY ) + $data = $this->_pdf->get_buffer(); + + else { + $data = file_get_contents($this->_file); + + //debugpng + if (DEBUGPNG) print '[pdflib output unlink '.$this->_file.']'; + if (!DEBUGKEEPTEMP) + + unlink($this->_file); + $this->_file = null; + unset($this->_file); + } + + return $data; + } +} + +// Workaround for idiotic limitation on statics... +PDFLib_Adapter::$PAPER_SIZES = CPDF_Adapter::$PAPER_SIZES; diff --git a/library/vendor/dompdf/include/php_evaluator.cls.php b/library/vendor/dompdf/include/php_evaluator.cls.php new file mode 100644 index 000000000..38896675d --- /dev/null +++ b/library/vendor/dompdf/include/php_evaluator.cls.php @@ -0,0 +1,48 @@ + + * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + */ + +/** + * Executes inline PHP code during the rendering process + * + * @access private + * @package dompdf + */ +class PHP_Evaluator { + + /** + * @var Canvas + */ + protected $_canvas; + + function __construct(Canvas $canvas) { + $this->_canvas = $canvas; + } + + function evaluate($code, $vars = array()) { + if ( !$this->_canvas->get_dompdf()->get_option("enable_php") ) { + return; + } + + // Set up some variables for the inline code + $pdf = $this->_canvas; + $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; + } + + //$code = html_entity_decode($code); // @todo uncomment this when tested + eval($code); + } + + function render(Frame $frame) { + $this->evaluate($frame->get_node()->nodeValue); + } +} diff --git a/library/vendor/dompdf/include/positioner.cls.php b/library/vendor/dompdf/include/positioner.cls.php new file mode 100644 index 000000000..6a8b9edc1 --- /dev/null +++ b/library/vendor/dompdf/include/positioner.cls.php @@ -0,0 +1,51 @@ + + * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + */ + +/** + * Base Positioner class + * + * Defines postioner interface + * + * @access private + * @package dompdf + */ +abstract class Positioner { + + /** + * @var Frame_Decorator + */ + protected $_frame; + + //........................................................................ + + function __construct(Frame_Decorator $frame) { + $this->_frame = $frame; + } + + /** + * Class destructor + */ + function __destruct() { + clear_object($this); + } + //........................................................................ + + abstract function position(); + + function move($offset_x, $offset_y, $ignore_self = false) { + list($x, $y) = $this->_frame->get_position(); + + if ( !$ignore_self ) { + $this->_frame->set_position($x + $offset_x, $y + $offset_y); + } + + foreach($this->_frame->get_children() as $child) { + $child->move($offset_x, $offset_y); + } + } +} diff --git a/library/vendor/dompdf/include/renderer.cls.php b/library/vendor/dompdf/include/renderer.cls.php new file mode 100644 index 000000000..ceff4775c --- /dev/null +++ b/library/vendor/dompdf/include/renderer.cls.php @@ -0,0 +1,290 @@ + + * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + */ + +/** + * Concrete renderer + * + * Instantiates several specific renderers in order to render any given + * frame. + * + * @access private + * @package dompdf + */ +class Renderer extends Abstract_Renderer { + + /** + * Array of renderers for specific frame types + * + * @var Abstract_Renderer[] + */ + protected $_renderers; + + /** + * Cache of the callbacks array + * + * @var array + */ + private $_callbacks; + + /** + * Class destructor + */ + function __destruct() { + clear_object($this); + } + + /** + * Advance the canvas to the next page + */ + function new_page() { + $this->_canvas->new_page(); + } + + /** + * Render frames recursively + * + * @param Frame $frame the frame to render + */ + function render(Frame $frame) { + global $_dompdf_debug; + + if ( $_dompdf_debug ) { + echo $frame; + flush(); + } + + $style = $frame->get_style(); + + if ( in_array($style->visibility, array("hidden", "collapse")) ) { + return; + } + + $display = $style->display; + + // Starts the CSS transformation + if ( $style->transform && is_array($style->transform) ) { + $this->_canvas->save(); + list($x, $y) = $frame->get_padding_box(); + $origin = $style->transform_origin; + + foreach($style->transform as $transform) { + list($function, $values) = $transform; + if ( $function === "matrix" ) { + $function = "transform"; + } + + $values = array_map("floatval", $values); + $values[] = $x + $style->length_in_pt($origin[0], $style->width); + $values[] = $y + $style->length_in_pt($origin[1], $style->height); + + call_user_func_array(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" ) { + list($x, $y, $w, $h) = $frame->get_padding_box(); + + // get border radii + $style = $frame->get_style(); + list($tl, $tr, $br, $bl) = $style->get_computed_border_radius($w, $h); + + if ( $tl + $tr + $br + $bl > 0 ) { + $this->_canvas->clipping_roundrectangle($x, $y, $w, $h, $tl, $tr, $br, $bl); + } + else { + $this->_canvas->clipping_rectangle($x, $y, $w, $h); + } + } + + $stack = array(); + + foreach ($frame->get_children() as $child) { + // < 0 : nagative 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 = intval($child_z_index) + 1; + } + elseif ( $child_style->float !== "none" || $child->is_positionned()) { + $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 ( $style->transform && is_array($style->transform) ) { + $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($event, $frame) { + if (!isset($this->_callbacks)) { + $this->_callbacks = $this->_dompdf->get_callbacks(); + } + + if (is_array($this->_callbacks) && isset($this->_callbacks[$event])) { + $info = array(0 => $this->_canvas, "canvas" => $this->_canvas, + 1 => $frame, "frame" => $frame); + $fs = $this->_callbacks[$event]; + foreach ($fs as $f) { + if (is_callable($f)) { + if (is_array($f)) { + $f[0]->$f[1]($info); + } else { + $f($info); + } + } + } + } + } + + /** + * 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_Renderer($this->_dompdf); + break; + + case "inline": + $this->_renderers[$type] = new Inline_Renderer($this->_dompdf); + break; + + case "text": + $this->_renderers[$type] = new Text_Renderer($this->_dompdf); + break; + + case "image": + $this->_renderers[$type] = new Image_Renderer($this->_dompdf); + break; + + case "table-cell": + $this->_renderers[$type] = new Table_Cell_Renderer($this->_dompdf); + break; + + case "table-row-group": + $this->_renderers[$type] = new Table_Row_Group_Renderer($this->_dompdf); + break; + + case "list-bullet": + $this->_renderers[$type] = new List_Bullet_Renderer($this->_dompdf); + break; + + case "php": + $this->_renderers[$type] = new PHP_Evaluator($this->_canvas); + break; + + case "javascript": + $this->_renderers[$type] = new Javascript_Embedder($this->_dompdf); + break; + + } + } + + $this->_renderers[$type]->render($frame); + + } +} diff --git a/library/vendor/dompdf/include/style.cls.php b/library/vendor/dompdf/include/style.cls.php new file mode 100644 index 000000000..d08fb4baa --- /dev/null +++ b/library/vendor/dompdf/include/style.cls.php @@ -0,0 +1,2435 @@ + + * @author Helmut Tischer + * @author Fabien Ménager + * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + */ + +/** + * 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 properites. + * + * Actual CSS parsing is performed in the {@link Stylesheet} class. + * + * @package dompdf + */ +class Style { + + const CSS_IDENTIFIER = "-?[_a-zA-Z]+[_a-zA-Z0-9-]*"; + const CSS_INTEGER = "-?\d+"; + + /** + * Default font size, in points. + * + * @var float + */ + static $default_font_size = 12; + + /** + * Default line height, as a fraction of the font size. + * + * @var float + */ + static $default_line_height = 1.2; + + /** + * Default "absolute" font sizes relative to the default font-size + * http://www.w3.org/TR/css3-fonts/#font-size-the-font-size-property + * @var array + */ + static $font_size_keywords = array( + "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 all inline types. Should really be a constant. + * + * @var array + */ + static $INLINE_TYPES = array("inline"); + + /** + * List of all block types. Should really be a constant. + * + * @var array + */ + static $BLOCK_TYPES = array("block", "inline-block", "table-cell", "list-item"); + + /** + * List of all positionned types. Should really be a constant. + * + * @var array + */ + static $POSITIONNED_TYPES = array("relative", "absolute", "fixed"); + + /** + * List of all table types. Should really be a constant. + * + * @var array; + */ + static $TABLE_TYPES = array("table", "inline-table"); + + /** + * List of valid border styles. Should also really be a constant. + * + * @var array + */ + static $BORDER_STYLES = array("none", "hidden", "dotted", "dashed", "solid", + "double", "groove", "ridge", "inset", "outset"); + + /** + * Default style values. + * + * @link http://www.w3.org/TR/CSS21/propidx.html + * + * @var array + */ + static protected $_defaults = null; + + /** + * List of inherited properties + * + * @link http://www.w3.org/TR/CSS21/propidx.html + * + * @var array + */ + static protected $_inherited = null; + + /** + * Caches method_exists result + * + * @var array + */ + static protected $_methods_cache = array(); + + /** + * The stylesheet this style belongs to + * + * @see Stylesheet + * @var Stylesheet + */ + protected $_stylesheet; // stylesheet this style is attached to + + /** + * Main array of all CSS properties & values + * + * @var array + */ + protected $_props; + + /* var instead of protected would allow access outside of class */ + protected $_important_props; + + /** + * Cached property values + * + * @var array + */ + protected $_prop_cache; + + /** + * Font size of parent element in document tree. Used for relative font + * size resolution. + * + * @var float + */ + protected $_parent_font_size; // Font size of parent element + + protected $_font_family; + + /** + * @var Frame + */ + protected $_frame; + + /** + * The origin of the style + * + * @var int + */ + protected $_origin = Stylesheet::ORIG_AUTHOR; + + // private members + /** + * True once the font size is resolved absolutely + * + * @var bool + */ + private $__font_size_calculated; // Cache flag + + /** + * The computed border radius + */ + private $_computed_border_radius = null; + + /** + * @var bool + */ + public $_has_border_radius = false; + + /** + * Class constructor + * + * @param Stylesheet $stylesheet the stylesheet this Style is associated with. + * @param int $origin + */ + function __construct(Stylesheet $stylesheet, $origin = Stylesheet::ORIG_AUTHOR) { + $this->_props = array(); + $this->_important_props = array(); + $this->_stylesheet = $stylesheet; + $this->_origin = $origin; + $this->_parent_font_size = null; + $this->__font_size_calculated = false; + + if ( !isset(self::$_defaults) ) { + + // Shorthand + $d =& self::$_defaults; + + // All CSS 2.1 properties, and their default values + $d["azimuth"] = "center"; + $d["background_attachment"] = "scroll"; + $d["background_color"] = "transparent"; + $d["background_image"] = "none"; + $d["background_image_resolution"] = "normal"; + $d["_dompdf_background_image_resolution"] = $d["background_image_resolution"]; + $d["background_position"] = "0% 0%"; + $d["background_repeat"] = "repeat"; + $d["background"] = ""; + $d["border_collapse"] = "separate"; + $d["border_color"] = ""; + $d["border_spacing"] = "0"; + $d["border_style"] = ""; + $d["border_top"] = ""; + $d["border_right"] = ""; + $d["border_bottom"] = ""; + $d["border_left"] = ""; + $d["border_top_color"] = ""; + $d["border_right_color"] = ""; + $d["border_bottom_color"] = ""; + $d["border_left_color"] = ""; + $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"] = "medium"; + $d["border_bottom_left_radius"] = ""; + $d["border_bottom_right_radius"] = ""; + $d["border_top_left_radius"] = ""; + $d["border_top_right_radius"] = ""; + $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()->get_option("default_font"); + $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["_dompdf_image_resolution"] = $d["image_resolution"]; + $d["_dompdf_keep"] = ""; + $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"; + $d["margin_left"] = "0"; + $d["margin_top"] = "0"; + $d["margin_bottom"] = "0"; + $d["margin"] = ""; + $d["max_height"] = "none"; + $d["max_width"] = "none"; + $d["min_height"] = "0"; + $d["min_width"] = "0"; + $d["opacity"] = "1.0"; // CSS3 + $d["orphans"] = "2"; + $d["outline_color"] = ""; // "invert" special color is not supported + $d["outline_style"] = "none"; + $d["outline_width"] = "medium"; + $d["outline"] = ""; + $d["overflow"] = "visible"; + $d["padding_top"] = "0"; + $d["padding_right"] = "0"; + $d["padding_bottom"] = "0"; + $d["padding_left"] = "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"] = ""; + $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"] = "left"; + $d["text_decoration"] = "none"; + $d["text_indent"] = "0"; + $d["text_transform"] = "none"; + $d["top"] = "auto"; + $d["transform"] = "none"; // CSS3 + $d["transform_origin"] = "50% 50%"; // CSS3 + $d["_webkit_transform"] = $d["transform"]; // CSS3 + $d["_webkit_transform_origin"] = $d["transform_origin"]; // CSS3 + $d["unicode_bidi"] = "normal"; + $d["vertical_align"] = "baseline"; + $d["visibility"] = "visible"; + $d["voice_family"] = ""; + $d["volume"] = "medium"; + $d["white_space"] = "normal"; + $d["word_wrap"] = "normal"; + $d["widows"] = "2"; + $d["width"] = "auto"; + $d["word_spacing"] = "normal"; + $d["z_index"] = "auto"; + + // for @font-face + $d["src"] = ""; + $d["unicode_range"] = ""; + + // Properties that inherit by default + self::$_inherited = array( + "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", + "page_break_inside", + "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", + "word_wrap", + "widows", + "word_spacing", + ); + } + } + + /** + * "Destructor": forcibly free all references held by this object + */ + function dispose() { + clear_object($this); + } + + function set_frame(Frame $frame) { + $this->_frame = $frame; + } + + function get_frame() { + return $this->_frame; + } + + function set_origin($origin) { + $this->_origin = $origin; + } + + function get_origin() { + return $this->_origin; + } + + /** + * returns the {@link Stylesheet} this Style is associated with. + * + * @return Stylesheet + */ + function get_stylesheet() { return $this->_stylesheet; } + + /** + * 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 a reference size is not provided, the default font size is used + * ({@link Style::$default_font_size}). + * + * @param float|array $length the length or array of lengths to resolve + * @param float $ref_size an absolute reference size to resolve percentage lengths + * @return float + */ + function length_in_pt($length, $ref_size = null) { + static $cache = array(); + + if ( !is_array($length) ) { + $length = array($length); + } + + if ( !isset($ref_size) ) { + $ref_size = self::$default_font_size; + } + + $key = implode("@", $length)."/$ref_size"; + + if ( isset($cache[$key]) ) { + return $cache[$key]; + } + + $ret = 0; + foreach ($length as $l) { + + if ( $l === "auto" ) { + return "auto"; + } + + if ( $l === "none" ) { + return "none"; + } + + // Assume numeric values are already in points + if ( is_numeric($l) ) { + $ret += $l; + continue; + } + + if ( $l === "normal" ) { + $ret += $ref_size; + continue; + } + + // Border lengths + if ( $l === "thin" ) { + $ret += 0.5; + continue; + } + + if ( $l === "medium" ) { + $ret += 1.5; + continue; + } + + if ( $l === "thick" ) { + $ret += 2.5; + continue; + } + + if ( ($i = mb_strpos($l, "px")) !== false ) { + $dpi = $this->_stylesheet->get_dompdf()->get_option("dpi"); + $ret += ( mb_substr($l, 0, $i) * 72 ) / $dpi; + continue; + } + + if ( ($i = mb_strpos($l, "pt")) !== false ) { + $ret += (float)mb_substr($l, 0, $i); + continue; + } + + if ( ($i = mb_strpos($l, "%")) !== false ) { + $ret += (float)mb_substr($l, 0, $i)/100 * $ref_size; + continue; + } + + if ( ($i = mb_strpos($l, "rem")) !== false ) { + $ret += (float)mb_substr($l, 0, $i) * $this->_stylesheet->get_dompdf()->get_tree()->get_root()->get_style()->font_size; + continue; + } + + if ( ($i = mb_strpos($l, "em")) !== false ) { + $ret += (float)mb_substr($l, 0, $i) * $this->__get("font_size"); + continue; + } + + if ( ($i = mb_strpos($l, "cm")) !== false ) { + $ret += mb_substr($l, 0, $i) * 72 / 2.54; + continue; + } + + if ( ($i = mb_strpos($l, "mm")) !== false ) { + $ret += mb_substr($l, 0, $i) * 72 / 25.4; + continue; + } + + // FIXME: em:ex ratio? + if ( ($i = mb_strpos($l, "ex")) !== false ) { + $ret += mb_substr($l, 0, $i) * $this->__get("font_size") / 2; + continue; + } + + if ( ($i = mb_strpos($l, "in")) !== false ) { + $ret += (float)mb_substr($l, 0, $i) * 72; + continue; + } + + if ( ($i = mb_strpos($l, "pc")) !== false ) { + $ret += (float)mb_substr($l, 0, $i) * 12; + continue; + } + + // Bogus value + $ret += $ref_size; + } + + return $cache[$key] = $ret; + } + + + /** + * Set inherited properties in this style using values in $parent + * + * @param Style $parent + * + * @return Style + */ + function inherit(Style $parent) { + + // Set parent font size + $this->_parent_font_size = $parent->get_font_size(); + + foreach (self::$_inherited as $prop) { + //inherit the !important property also. + //if local property is also !important, don't inherit. + if ( isset($parent->_props[$prop]) && + ( !isset($this->_props[$prop]) || + ( isset($parent->_important_props[$prop]) && !isset($this->_important_props[$prop]) ) + ) + ) { + if ( isset($parent->_important_props[$prop]) ) { + $this->_important_props[$prop] = true; + } + //see __set and __get, on all assignments clear cache! + $this->_prop_cache[$prop] = null; + $this->_props[$prop] = $parent->_props[$prop]; + } + } + + foreach ($this->_props as $prop => $value) { + if ( $value === "inherit" ) { + if ( isset($parent->_important_props[$prop]) ) { + $this->_important_props[$prop] = true; + } + //do not assign direct, but + //implicite assignment through __set, redirect to specialized, get value with __get + //This is for computing defaults if the parent setting is also missing. + //Therefore do not directly assign the value without __set + //set _important_props before that to be able to propagate. + //see __set and __get, on all assignments clear cache! + //$this->_prop_cache[$prop] = null; + //$this->_props[$prop] = $parent->_props[$prop]; + //props_set for more obvious explicite assignment not implemented, because + //too many implicite uses. + // $this->props_set($prop, $parent->$prop); + $this->__set($prop, $parent->__get($prop)); + } + } + + return $this; + } + + /** + * Override properties in this style with those in $style + * + * @param Style $style + */ + function merge(Style $style) { + //treat the !important attribute + //if old rule has !important attribute, override with new rule only if + //the new rule is also !important + foreach($style->_props as $prop => $val ) { + if (isset($style->_important_props[$prop])) { + $this->_important_props[$prop] = true; + //see __set and __get, on all assignments clear cache! + $this->_prop_cache[$prop] = null; + $this->_props[$prop] = $val; + } + else if ( !isset($this->_important_props[$prop]) ) { + //see __set and __get, on all assignments clear cache! + $this->_prop_cache[$prop] = null; + $this->_props[$prop] = $val; + } + } + + if ( isset($style->_props["font_size"]) ) { + $this->__font_size_calculated = false; + } + } + + /** + * Returns an array(r, g, b, "r"=> r, "g"=>g, "b"=>b, "hex"=>"#rrggbb") + * based on the provided CSS color value. + * + * @param string $color + * @return array + */ + function munge_color($color) { + return CSS_Color::parse($color); + } + + /* direct access to _important_props array from outside would work only when declared as + * 'var $_important_props;' instead of 'protected $_important_props;' + * Don't call _set/__get on missing attribute. Therefore need a special access. + * Assume that __set will be also called when this is called, so do not check validity again. + * Only created, if !important exists -> always set true. + */ + function important_set($prop) { + $prop = str_replace("-", "_", $prop); + $this->_important_props[$prop] = true; + } + + function important_get($prop) { + return isset($this->_important_props[$prop]); + } + + /** + * PHP5 overloaded setter + * + * This function along with {@link Style::__get()} permit a user of the + * Style class to access any (CSS) property using the following syntax: + * + * Style->margin_top = "1em"; + * echo (Style->margin_top); + * + * + * __set() automatically calls the provided set function, if one exists, + * otherwise it sets the property directly. Typically, __set() is not + * called directly from outside of this class. + * + * On each modification clear cache to return accurate setting. + * Also affects direct settings not using __set + * For easier finding all assignments, attempted to allowing only explicite assignment: + * Very many uses, e.g. frame_reflower.cls.php -> for now leave as it is + * function __set($prop, $val) { + * throw new DOMPDF_Exception("Implicite replacement of assignment by __set. Not good."); + * } + * function props_set($prop, $val) { ... } + * + * @param string $prop the property to set + * @param mixed $val the value of the property + * + */ + function __set($prop, $val) { + $prop = str_replace("-", "_", $prop); + $this->_prop_cache[$prop] = null; + + if ( !isset(self::$_defaults[$prop]) ) { + global $_dompdf_warnings; + $_dompdf_warnings[] = "'$prop' is not a valid CSS2 property."; + return; + } + + if ( $prop !== "content" && is_string($val) && strlen($val) > 5 && mb_strpos($val, "url") === false ) { + $val = mb_strtolower(trim(str_replace(array("\n", "\t"), array(" "), $val))); + $val = preg_replace("/([0-9]+) (pt|px|pc|em|ex|in|cm|mm|%)/S", "\\1\\2", $val); + } + + $method = "set_$prop"; + + if ( !isset(self::$_methods_cache[$method]) ) { + self::$_methods_cache[$method] = method_exists($this, $method); + } + + if ( self::$_methods_cache[$method] ) { + $this->$method($val); + } + else { + $this->_props[$prop] = $val; + } + } + + /** + * PHP5 overloaded getter + * Along with {@link Style::__set()} __get() provides access to all CSS + * properties directly. Typically __get() is not called directly outside + * of this class. + * On each modification clear cache to return accurate setting. + * Also affects direct settings not using __set + * + * @param string $prop + * + * @throws DOMPDF_Exception + * @return mixed + */ + function __get($prop) { + if ( !isset(self::$_defaults[$prop]) ) { + throw new DOMPDF_Exception("'$prop' is not a valid CSS2 property."); + } + + if ( isset($this->_prop_cache[$prop]) && $this->_prop_cache[$prop] != null ) { + return $this->_prop_cache[$prop]; + } + + $method = "get_$prop"; + + // Fall back on defaults if property is not set + if ( !isset($this->_props[$prop]) ) { + $this->_props[$prop] = self::$_defaults[$prop]; + } + + if ( !isset(self::$_methods_cache[$method]) ) { + self::$_methods_cache[$method] = method_exists($this, $method); + } + + if ( self::$_methods_cache[$method] ) { + return $this->_prop_cache[$prop] = $this->$method(); + } + + return $this->_prop_cache[$prop] = $this->_props[$prop]; + } + + function get_font_family_raw(){ + return trim($this->_props["font_family"], " \t\n\r\x0B\"'"); + } + + /** + * Getter for the 'font-family' CSS property. + * Uses the {@link Font_Metrics} class to resolve the font family into an + * actual font file. + * + * @link http://www.w3.org/TR/CSS21/fonts.html#propdef-font-family + * @throws DOMPDF_Exception + * + * @return string + */ + function get_font_family() { + if ( isset($this->_font_family) ) { + return $this->_font_family; + } + + $DEBUGCSS=DEBUGCSS; //=DEBUGCSS; Allow override of global setting for ad hoc debug + + // 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 ( is_numeric($weight) ) { + if ( $weight < 600 ) { + $weight = "normal"; + } + else { + $weight = "bold"; + } + } + else if ( $weight === "bold" || $weight === "bolder" ) { + $weight = "bold"; + } + else { + $weight = "normal"; + } + + // Resolve font-style + $font_style = $this->__get("font_style"); + + if ( $weight === "bold" && ($font_style === "italic" || $font_style === "oblique") ) { + $subtype = "bold_italic"; + } + else if ( $weight === "bold" && $font_style !== "italic" && $font_style !== "oblique" ) { + $subtype = "bold"; + } + else if ( $weight !== "bold" && ($font_style === "italic" || $font_style === "oblique") ) { + $subtype = "italic"; + } + else { + $subtype = "normal"; + } + + // Resolve the font family + if ( $DEBUGCSS ) { + print "
[get_font_family:";
+      print '('.$this->_props["font_family"].'.'.$font_style.'.'.$this->__get("font_weight").'.'.$weight.'.'.$subtype.')';
+    }
+    
+    $families = preg_split("/\s*,\s*/", $this->_props["font_family"]);
+
+    $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 = Font_Metrics::get_font($family, $subtype);
+
+      if ( $font ) {
+        if ($DEBUGCSS) print '('.$font.")get_font_family]\n
"; + return $this->_font_family = $font; + } + } + + $family = null; + if ( $DEBUGCSS ) { + print '(default)'; + } + $font = Font_Metrics::get_font($family, $subtype); + + if ( $font ) { + if ( $DEBUGCSS ) print '('.$font.")get_font_family]\n"; + return$this->_font_family = $font; + } + + throw new DOMPDF_Exception("Unable to find a suitable font replacement for: '" . $this->_props["font_family"] ."'"); + + } + + /** + * Returns the resolved font size, in points + * + * @link http://www.w3.org/TR/CSS21/fonts.html#propdef-font-size + * @return float + */ + function get_font_size() { + + if ( $this->__font_size_calculated ) { + return $this->_props["font_size"]; + } + + if ( !isset($this->_props["font_size"]) ) { + $fs = self::$_defaults["font_size"]; + } + else { + $fs = $this->_props["font_size"]; + } + + if ( !isset($this->_parent_font_size) ) { + $this->_parent_font_size = self::$default_font_size; + } + + switch ((string)$fs) { + 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[$fs]; + break; + + case "smaller": + $fs = 8/9 * $this->_parent_font_size; + break; + + case "larger": + $fs = 6/5 * $this->_parent_font_size; + break; + + default: + break; + } + + // Ensure relative sizes resolve to something + if ( ($i = mb_strpos($fs, "em")) !== false ) { + $fs = mb_substr($fs, 0, $i) * $this->_parent_font_size; + } + else if ( ($i = mb_strpos($fs, "ex")) !== false ) { + $fs = mb_substr($fs, 0, $i) * $this->_parent_font_size; + } + else { + $fs = $this->length_in_pt($fs); + } + + //see __set and __get, on all assignments clear cache! + $this->_prop_cache["font_size"] = null; + $this->_props["font_size"] = $fs; + $this->__font_size_calculated = true; + return $this->_props["font_size"]; + + } + + /** + * @link http://www.w3.org/TR/CSS21/text.html#propdef-word-spacing + * @return float + */ + function get_word_spacing() { + if ( $this->_props["word_spacing"] === "normal" ) { + return 0; + } + + return $this->_props["word_spacing"]; + } + + /** + * @link http://www.w3.org/TR/CSS21/text.html#propdef-letter-spacing + * @return float + */ + function get_letter_spacing() { + if ( $this->_props["letter_spacing"] === "normal" ) { + return 0; + } + + return $this->_props["letter_spacing"]; + } + + /** + * @link http://www.w3.org/TR/CSS21/visudet.html#propdef-line-height + * @return float + */ + function get_line_height() { + $line_height = $this->_props["line_height"]; + + if ( $line_height === "normal" ) { + return self::$default_line_height * $this->get_font_size(); + } + + if ( is_numeric($line_height) ) { + return $this->length_in_pt( $line_height . "em", $this->get_font_size()); + } + + return $this->length_in_pt( $line_height, $this->_parent_font_size ); + } + + /** + * Returns the color as an array + * + * The array has the following format: + * array(r,g,b, "r" => r, "g" => g, "b" => b, "hex" => "#rrggbb") + * + * @link http://www.w3.org/TR/CSS21/colors.html#propdef-color + * @return array + */ + function get_color() { + return $this->munge_color( $this->_props["color"] ); + } + + /** + * Returns the background color as an array + * + * The returned array has the same format as {@link Style::get_color()} + * + * @link http://www.w3.org/TR/CSS21/colors.html#propdef-background-color + * @return array + */ + function get_background_color() { + return $this->munge_color( $this->_props["background_color"] ); + } + + /** + * Returns the background position as an array + * + * The returned array has the following format: + * array(x,y, "x" => x, "y" => y) + * + * @link http://www.w3.org/TR/CSS21/colors.html#propdef-background-position + * @return array + */ + function get_background_position() { + $tmp = explode(" ", $this->_props["background_position"]); + + switch ($tmp[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 = $tmp[0]; + break; + } + + if ( isset($tmp[1]) ) { + + switch ($tmp[1]) { + case "left": + $x = "0%"; + break; + + case "right": + $x = "100%"; + break; + + case "top": + $y = "0%"; + break; + + case "bottom": + $y = "100%"; + break; + + case "center": + if ( $tmp[0] === "left" || $tmp[0] === "right" || $tmp[0] === "center" ) { + $y = "50%"; + } + else { + $x = "50%"; + } + break; + + default: + $y = $tmp[1]; + break; + } + + } + else { + $y = "50%"; + } + + if ( !isset($x) ) { + $x = "0%"; + } + + if ( !isset($y) ) { + $y = "0%"; + } + + return array( + 0 => $x, "x" => $x, + 1 => $y, "y" => $y, + ); + } + + + /** + * Returns the background as it is currently stored + * + * (currently anyway only for completeness. + * not used for further processing) + * + * @link http://www.w3.org/TR/CSS21/colors.html#propdef-background-attachment + * @return string + */ + function get_background_attachment() { + return $this->_props["background_attachment"]; + } + + + /** + * Returns the background_repeat as it is currently stored + * + * (currently anyway only for completeness. + * not used for further processing) + * + * @link http://www.w3.org/TR/CSS21/colors.html#propdef-background-repeat + * @return string + */ + function get_background_repeat() { + return $this->_props["background_repeat"]; + } + + + /** + * Returns the background as it is currently stored + * + * (currently anyway only for completeness. + * not used for further processing, but the individual get_background_xxx) + * + * @link http://www.w3.org/TR/CSS21/colors.html#propdef-background + * @return string + */ + function get_background() { + return $this->_props["background"]; + } + + + /**#@+ + * Returns the border color as an array + * + * See {@link Style::get_color()} + * + * @link http://www.w3.org/TR/CSS21/box.html#border-color-properties + * @return array + */ + function get_border_top_color() { + if ( $this->_props["border_top_color"] === "" ) { + //see __set and __get, on all assignments clear cache! + $this->_prop_cache["border_top_color"] = null; + $this->_props["border_top_color"] = $this->__get("color"); + } + + return $this->munge_color($this->_props["border_top_color"]); + } + + function get_border_right_color() { + if ( $this->_props["border_right_color"] === "" ) { + //see __set and __get, on all assignments clear cache! + $this->_prop_cache["border_right_color"] = null; + $this->_props["border_right_color"] = $this->__get("color"); + } + + return $this->munge_color($this->_props["border_right_color"]); + } + + function get_border_bottom_color() { + if ( $this->_props["border_bottom_color"] === "" ) { + //see __set and __get, on all assignments clear cache! + $this->_prop_cache["border_bottom_color"] = null; + $this->_props["border_bottom_color"] = $this->__get("color"); + } + + return $this->munge_color($this->_props["border_bottom_color"]); + } + + function get_border_left_color() { + if ( $this->_props["border_left_color"] === "" ) { + //see __set and __get, on all assignments clear cache! + $this->_prop_cache["border_left_color"] = null; + $this->_props["border_left_color"] = $this->__get("color"); + } + + return $this->munge_color($this->_props["border_left_color"]); + } + + /**#@-*/ + + /**#@+ + * Returns the border width, as it is currently stored + * + * @link http://www.w3.org/TR/CSS21/box.html#border-width-properties + * @return float|string + */ + function get_border_top_width() { + $style = $this->__get("border_top_style"); + return $style !== "none" && $style !== "hidden" ? $this->length_in_pt($this->_props["border_top_width"]) : 0; + } + + function get_border_right_width() { + $style = $this->__get("border_right_style"); + return $style !== "none" && $style !== "hidden" ? $this->length_in_pt($this->_props["border_right_width"]) : 0; + } + + function get_border_bottom_width() { + $style = $this->__get("border_bottom_style"); + return $style !== "none" && $style !== "hidden" ? $this->length_in_pt($this->_props["border_bottom_width"]) : 0; + } + + function get_border_left_width() { + $style = $this->__get("border_left_style"); + return $style !== "none" && $style !== "hidden" ? $this->length_in_pt($this->_props["border_left_width"]) : 0; + } + /**#@-*/ + + /** + * 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 + */ + function get_border_properties() { + return array( + "top" => array( + "width" => $this->__get("border_top_width"), + "style" => $this->__get("border_top_style"), + "color" => $this->__get("border_top_color"), + ), + "bottom" => array( + "width" => $this->__get("border_bottom_width"), + "style" => $this->__get("border_bottom_style"), + "color" => $this->__get("border_bottom_color"), + ), + "right" => array( + "width" => $this->__get("border_right_width"), + "style" => $this->__get("border_right_style"), + "color" => $this->__get("border_right_color"), + ), + "left" => array( + "width" => $this->__get("border_left_width"), + "style" => $this->__get("border_left_style"), + "color" => $this->__get("border_left_color"), + ), + ); + } + + /** + * Return a single border property + * + * @param string $side + * + * @return mixed + */ + protected function _get_border($side) { + $color = $this->__get("border_" . $side . "_color"); + + return $this->__get("border_" . $side . "_width") . " " . + $this->__get("border_" . $side . "_style") . " " . $color["hex"]; + } + + /**#@+ + * Return full border properties as a string + * + * Border properties are returned just as specified in CSS: + *
[width] [style] [color]
+ * e.g. "1px solid blue" + * + * @link http://www.w3.org/TR/CSS21/box.html#border-shorthand-properties + * @return string + */ + function get_border_top() { + return $this->_get_border("top"); + } + + function get_border_right() { + return $this->_get_border("right"); + } + + function get_border_bottom() { + return $this->_get_border("bottom"); + } + + function get_border_left() { + return $this->_get_border("left"); + } + /**#@-*/ + + function get_computed_border_radius($w, $h) { + if ( !empty($this->_computed_border_radius) ) { + return $this->_computed_border_radius; + } + + $rTL = $this->__get("border_top_left_radius"); + $rTR = $this->__get("border_top_right_radius"); + $rBL = $this->__get("border_bottom_left_radius"); + $rBR = $this->__get("border_bottom_right_radius"); + + if ( $rTL + $rTR + $rBL + $rBR == 0 ) { + return $this->_computed_border_radius = array( + 0, 0, 0, 0, + "top-left" => 0, + "top-right" => 0, + "bottom-right" => 0, + "bottom-left" => 0, + ); + } + + $t = $this->__get("border_top_width"); + $r = $this->__get("border_right_width"); + $b = $this->__get("border_bottom_width"); + $l = $this->__get("border_left_width"); + + $rTL = min($rTL, $h - $rBL - $t/2 - $b/2, $w - $rTR - $l/2 - $r/2); + $rTR = min($rTR, $h - $rBR - $t/2 - $b/2, $w - $rTL - $l/2 - $r/2); + $rBL = min($rBL, $h - $rTL - $t/2 - $b/2, $w - $rBR - $l/2 - $r/2); + $rBR = min($rBR, $h - $rTR - $t/2 - $b/2, $w - $rBL - $l/2 - $r/2); + + return $this->_computed_border_radius = array( + $rTL, $rTR, $rBR, $rBL, + "top-left" => $rTL, + "top-right" => $rTR, + "bottom-right" => $rBR, + "bottom-left" => $rBL, + ); + } + /**#@-*/ + + + /** + * Returns the outline color as an array + * + * See {@link Style::get_color()} + * + * @link http://www.w3.org/TR/CSS21/box.html#border-color-properties + * @return array + */ + function get_outline_color() { + if ( $this->_props["outline_color"] === "" ) { + //see __set and __get, on all assignments clear cache! + $this->_prop_cache["outline_color"] = null; + $this->_props["outline_color"] = $this->__get("color"); + } + + return $this->munge_color($this->_props["outline_color"]); + } + + /**#@+ + * Returns the outline width, as it is currently stored + * @return float|string + */ + function get_outline_width() { + $style = $this->__get("outline_style"); + return $style !== "none" && $style !== "hidden" ? $this->length_in_pt($this->_props["outline_width"]) : 0; + } + + /**#@+ + * Return full outline properties as a string + * + * Outline properties are returned just as specified in CSS: + *
[width] [style] [color]
+ * e.g. "1px solid blue" + * + * @link http://www.w3.org/TR/CSS21/box.html#border-shorthand-properties + * @return string + */ + function get_outline() { + $color = $this->__get("outline_color"); + return + $this->__get("outline_width") . " " . + $this->__get("outline_style") . " " . + $color["hex"]; + } + /**#@-*/ + + /** + * Returns border spacing as an array + * + * The array has the format (h_space,v_space) + * + * @link http://www.w3.org/TR/CSS21/tables.html#propdef-border-spacing + * @return array + */ + function get_border_spacing() { + $arr = explode(" ", $this->_props["border_spacing"]); + if ( count($arr) == 1 ) { + $arr[1] = $arr[0]; + } + return $arr; + } + +/*==============================*/ + + /* + !important attribute + For basic functionality of the !important attribute with overloading + of several styles of an element, changes in inherit(), merge() and _parse_properties() + are sufficient [helpers var $_important_props, __construct(), important_set(), important_get()] + + Only for combined attributes extra treatment needed. See below. + + div { border: 1px red; } + div { border: solid; } // Not combined! Only one occurence of same style per context + // + div { border: 1px red; } + div a { border: solid; } // Adding to border style ok by inheritance + // + div { border-style: solid; } // Adding to border style ok because of different styles + div { border: 1px red; } + // + div { border-style: solid; !important} // border: overrides, even though not !important + div { border: 1px dashed red; } + // + div { border: 1px red; !important } + div a { border-style: solid; } // Need to override because not set + + Special treatment: + At individual property like border-top-width need to check whether overriding value is also !important. + Also store the !important condition for later overrides. + Since not known who is initiating the override, need to get passed !important as parameter. + !important Paramter taken as in the original style in the css file. + When property border !important given, do not mark subsets like border_style as important. Only + individual properties. + + Note: + Setting individual property directly from css with e.g. set_border_top_style() is not needed, because + missing set funcions handled by a generic handler __set(), including the !important. + Setting individual property of as sub-property is handled below. + + Implementation see at _set_style_side_type() + Callers _set_style_sides_type(), _set_style_type, _set_style_type_important() + + Related functionality for background, padding, margin, font, list_style + */ + + /* Generalized set function for individual attribute of combined style. + * With check for !important + * Applicable for background, border, padding, margin, font, list_style + * Note: $type has a leading underscore (or is empty), the others not. + */ + protected function _set_style_side_type($style, $side, $type, $val, $important) { + $prop = $style.'_'.$side.$type; + + if ( !isset($this->_important_props[$prop]) || $important) { + //see __set and __get, on all assignments clear cache! + $this->_prop_cache[$prop] = null; + if ( $important ) { + $this->_important_props[$prop] = true; + } + $this->_props[$prop] = $val; + } + } + + protected function _set_style_sides_type($style,$top,$right,$bottom,$left,$type,$important) { + $this->_set_style_side_type($style,'top',$type,$top,$important); + $this->_set_style_side_type($style,'right',$type,$right,$important); + $this->_set_style_side_type($style,'bottom',$type,$bottom,$important); + $this->_set_style_side_type($style,'left',$type,$left,$important); + } + + protected function _set_style_type($style,$type,$val,$important) { + $val = preg_replace("/\s*\,\s*/", ",", $val); // when rgb() has spaces + $arr = explode(" ", $val); + + switch (count($arr)) { + case 1: $this->_set_style_sides_type($style,$arr[0],$arr[0],$arr[0],$arr[0],$type,$important); break; + case 2: $this->_set_style_sides_type($style,$arr[0],$arr[1],$arr[0],$arr[1],$type,$important); break; + case 3: $this->_set_style_sides_type($style,$arr[0],$arr[1],$arr[2],$arr[1],$type,$important); break; + case 4: $this->_set_style_sides_type($style,$arr[0],$arr[1],$arr[2],$arr[3],$type,$important); break; + } + + //see __set and __get, on all assignments clear cache! + $this->_prop_cache[$style.$type] = null; + $this->_props[$style.$type] = $val; + } + + protected function _set_style_type_important($style,$type,$val) { + $this->_set_style_type($style,$type,$val,isset($this->_important_props[$style.$type])); + } + + /* Anyway only called if _important matches and is assigned + * E.g. _set_style_side_type($style,$side,'',str_replace("none", "0px", $val),isset($this->_important_props[$style.'_'.$side])); + */ + protected function _set_style_side_width_important($style,$side,$val) { + //see __set and __get, on all assignments clear cache! + $this->_prop_cache[$style.'_'.$side] = null; + $this->_props[$style.'_'.$side] = str_replace("none", "0px", $val); + } + + protected function _set_style($style,$val,$important) { + if ( !isset($this->_important_props[$style]) || $important) { + if ( $important ) { + $this->_important_props[$style] = true; + } + //see __set and __get, on all assignments clear cache! + $this->_prop_cache[$style] = null; + $this->_props[$style] = $val; + } + } + + protected function _image($val) { + $DEBUGCSS=DEBUGCSS; + $parsed_url = "none"; + + if ( 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\(['\"]?([^'\")]+)['\"]?\)/","\\1", trim($val)); + + // Resolve the url now in the context of the current stylesheet + $parsed_url = explode_url($val); + if ( $parsed_url["protocol"] == "" && $this->_stylesheet->get_protocol() == "" ) { + if ($parsed_url["path"][0] === '/' || $parsed_url["path"][0] === '\\' ) { + $path = $_SERVER["DOCUMENT_ROOT"].'/'; + } + else { + $path = $this->_stylesheet->get_base_path(); + } + + $path .= $parsed_url["path"] . $parsed_url["file"]; + $path = realpath($path); + // If realpath returns FALSE then specifically state that there is no background image + if ( !$path ) { + $path = 'none'; + } + } + else { + $path = build_url($this->_stylesheet->get_protocol(), + $this->_stylesheet->get_host(), + $this->_stylesheet->get_base_path(), + $val); + } + } + if ($DEBUGCSS) { + print "
[_image\n";
+      print_r($parsed_url);
+      print $this->_stylesheet->get_protocol()."\n".$this->_stylesheet->get_base_path()."\n".$path."\n";
+      print "_image]
";; + } + return $path; + } + +/*======================*/ + + /** + * Sets color + * + * The color parameter can be any valid CSS color value + * + * @link http://www.w3.org/TR/CSS21/colors.html#propdef-color + * @param string $color + */ + function set_color($color) { + $col = $this->munge_color($color); + + if ( is_null($col) || !isset($col["hex"]) ) { + $color = "inherit"; + } + else { + $color = $col["hex"]; + } + + //see __set and __get, on all assignments clear cache, not needed on direct set through __set + $this->_prop_cache["color"] = null; + $this->_props["color"] = $color; + } + + /** + * Sets the background color + * + * @link http://www.w3.org/TR/CSS21/colors.html#propdef-background-color + * @param string $color + */ + function set_background_color($color) { + $col = $this->munge_color($color); + + if ( is_null($col) ) { + return; + //$col = self::$_defaults["background_color"]; + } + + //see __set and __get, on all assignments clear cache, not needed on direct set through __set + $this->_prop_cache["background_color"] = null; + $this->_props["background_color"] = is_array($col) ? $col["hex"] : $col; + } + + /** + * Set the background image url + * @link http://www.w3.org/TR/CSS21/colors.html#background-properties + * + * @param string $val + */ + function set_background_image($val) { + //see __set and __get, on all assignments clear cache, not needed on direct set through __set + $this->_prop_cache["background_image"] = null; + $this->_props["background_image"] = $this->_image($val); + } + + /** + * Sets the background repeat + * + * @link http://www.w3.org/TR/CSS21/colors.html#propdef-background-repeat + * @param string $val + */ + function set_background_repeat($val) { + if ( is_null($val) ) { + $val = self::$_defaults["background_repeat"]; + } + + //see __set and __get, on all assignments clear cache, not needed on direct set through __set + $this->_prop_cache["background_repeat"] = null; + $this->_props["background_repeat"] = $val; + } + + /** + * Sets the background attachment + * + * @link http://www.w3.org/TR/CSS21/colors.html#propdef-background-attachment + * @param string $val + */ + function set_background_attachment($val) { + if ( is_null($val) ) { + $val = self::$_defaults["background_attachment"]; + } + + //see __set and __get, on all assignments clear cache, not needed on direct set through __set + $this->_prop_cache["background_attachment"] = null; + $this->_props["background_attachment"] = $val; + } + + /** + * Sets the background position + * + * @link http://www.w3.org/TR/CSS21/colors.html#propdef-background-position + * @param string $val + */ + function set_background_position($val) { + if ( is_null($val) ) { + $val = self::$_defaults["background_position"]; + } + + //see __set and __get, on all assignments clear cache, not needed on direct set through __set + $this->_prop_cache["background_position"] = null; + $this->_props["background_position"] = $val; + } + + /** + * Sets the background - combined options + * + * @link http://www.w3.org/TR/CSS21/colors.html#propdef-background + * @param string $val + */ + function set_background($val) { + $val = trim($val); + $important = isset($this->_important_props["background"]); + + if ( $val === "none" ) { + $this->_set_style("background_image", "none", $important); + $this->_set_style("background_color", "transparent", $important); + } + else { + $pos = array(); + $tmp = preg_replace("/\s*\,\s*/", ",", $val); // when rgb() has spaces + $tmp = preg_split("/\s+/", $tmp); + + foreach($tmp as $attr) { + if ( mb_substr($attr, 0, 3) === "url" || $attr === "none" ) { + $this->_set_style("background_image", $this->_image($attr), $important); + } + elseif ( $attr === "fixed" || $attr === "scroll" ) { + $this->_set_style("background_attachment", $attr, $important); + } + elseif ( $attr === "repeat" || $attr === "repeat-x" || $attr === "repeat-y" || $attr === "no-repeat" ) { + $this->_set_style("background_repeat", $attr, $important); + } + elseif ( ($col = $this->munge_color($attr)) != null ) { + $this->_set_style("background_color", is_array($col) ? $col["hex"] : $col, $important); + } + else { + $pos[] = $attr; + } + } + + if (count($pos)) { + $this->_set_style("background_position", implode(" ", $pos), $important); + } + } + + //see __set and __get, on all assignments clear cache, not needed on direct set through __set + $this->_prop_cache["background"] = null; + $this->_props["background"] = $val; + } + + /** + * Sets the font size + * + * $size can be any acceptable CSS size + * + * @link http://www.w3.org/TR/CSS21/fonts.html#propdef-font-size + * @param string|float $size + */ + function set_font_size($size) { + $this->__font_size_calculated = false; + //see __set and __get, on all assignments clear cache, not needed on direct set through __set + $this->_prop_cache["font_size"] = null; + $this->_props["font_size"] = $size; + } + + /** + * Sets the font style + * + * combined attributes + * set individual attributes also, respecting !important mark + * exactly this order, separate by space. Multiple fonts separated by comma: + * font-style, font-variant, font-weight, font-size, line-height, font-family + * + * Other than with border and list, existing partial attributes should + * reset when starting here, even when not mentioned. + * If individual attribute is !important and explicite or implicite replacement is not, + * keep individual attribute + * + * require whitespace as delimiters for single value attributes + * On delimiter "/" treat first as font height, second as line height + * treat all remaining at the end of line as font + * font-style, font-variant, font-weight, font-size, line-height, font-family + * + * missing font-size and font-family might be not allowed, but accept it here and + * use default (medium size, enpty font name) + * + * @link http://www.w3.org/TR/CSS21/generate.html#propdef-list-style + * @param $val + */ + function set_font($val) { + $this->__font_size_calculated = false; + //see __set and __get, on all assignments clear cache, not needed on direct set through __set + $this->_prop_cache["font"] = null; + $this->_props["font"] = $val; + + $important = isset($this->_important_props["font"]); + + if ( preg_match("/^(italic|oblique|normal)\s*(.*)$/i",$val,$match) ) { + $this->_set_style("font_style", $match[1], $important); + $val = $match[2]; + } + else { + $this->_set_style("font_style", self::$_defaults["font_style"], $important); + } + + if ( preg_match("/^(small-caps|normal)\s*(.*)$/i",$val,$match) ) { + $this->_set_style("font_variant", $match[1], $important); + $val = $match[2]; + } + else { + $this->_set_style("font_variant", self::$_defaults["font_variant"], $important); + } + + //matching numeric value followed by unit -> this is indeed a subsequent font size. Skip! + if ( preg_match("/^(bold|bolder|lighter|100|200|300|400|500|600|700|800|900|normal)\s*(.*)$/i", $val, $match) && + !preg_match("/^(?:pt|px|pc|em|ex|in|cm|mm|%)/",$match[2]) + ) { + $this->_set_style("font_weight", $match[1], $important); + $val = $match[2]; + } + else { + $this->_set_style("font_weight", self::$_defaults["font_weight"], $important); + } + + if ( preg_match("/^(xx-small|x-small|small|medium|large|x-large|xx-large|smaller|larger|\d+\s*(?:pt|px|pc|em|ex|in|cm|mm|%))\s*(.*)$/i",$val,$match) ) { + $this->_set_style("font_size", $match[1], $important); + $val = $match[2]; + if ( preg_match("/^\/\s*(\d+\s*(?:pt|px|pc|em|ex|in|cm|mm|%))\s*(.*)$/i", $val, $match ) ) { + $this->_set_style("line_height", $match[1], $important); + $val = $match[2]; + } + else { + $this->_set_style("line_height", self::$_defaults["line_height"], $important); + } + } + else { + $this->_set_style("font_size", self::$_defaults["font_size"], $important); + $this->_set_style("line_height", self::$_defaults["line_height"], $important); + } + + if( strlen($val) != 0 ) { + $this->_set_style("font_family", $val, $important); + } + else { + $this->_set_style("font_family", self::$_defaults["font_family"], $important); + } + } + + /**#@+ + * Sets page break properties + * + * @link http://www.w3.org/TR/CSS21/page.html#page-breaks + * @param string $break + */ + function set_page_break_before($break) { + if ( $break === "left" || $break === "right" ) { + $break = "always"; + } + + //see __set and __get, on all assignments clear cache, not needed on direct set through __set + $this->_prop_cache["page_break_before"] = null; + $this->_props["page_break_before"] = $break; + } + + function set_page_break_after($break) { + if ( $break === "left" || $break === "right" ) { + $break = "always"; + } + + //see __set and __get, on all assignments clear cache, not needed on direct set through __set + $this->_prop_cache["page_break_after"] = null; + $this->_props["page_break_after"] = $break; + } + /**#@-*/ + + //........................................................................ + + /**#@+ + * Sets the margin size + * + * @link http://www.w3.org/TR/CSS21/box.html#margin-properties + * @param $val + */ + function set_margin_top($val) { + $this->_set_style_side_width_important('margin','top',$val); + } + + function set_margin_right($val) { + $this->_set_style_side_width_important('margin','right',$val); + } + + function set_margin_bottom($val) { + $this->_set_style_side_width_important('margin','bottom',$val); + } + + function set_margin_left($val) { + $this->_set_style_side_width_important('margin','left',$val); + } + + function set_margin($val) { + $val = str_replace("none", "0px", $val); + $this->_set_style_type_important('margin','',$val); + } + /**#@-*/ + + /**#@+ + * Sets the padding size + * + * @link http://www.w3.org/TR/CSS21/box.html#padding-properties + * @param $val + */ + function set_padding_top($val) { + $this->_set_style_side_width_important('padding','top',$val); + } + + function set_padding_right($val) { + $this->_set_style_side_width_important('padding','right',$val); + } + + function set_padding_bottom($val) { + $this->_set_style_side_width_important('padding','bottom',$val); + } + + function set_padding_left($val) { + $this->_set_style_side_width_important('padding','left',$val); + } + + function set_padding($val) { + $val = str_replace("none", "0px", $val); + $this->_set_style_type_important('padding','',$val); + } + /**#@-*/ + + /** + * Sets a single border + * + * @param string $side + * @param string $border_spec ([width] [style] [color]) + * @param boolean $important + */ + protected function _set_border($side, $border_spec, $important) { + $border_spec = preg_replace("/\s*\,\s*/", ",", $border_spec); + //$border_spec = str_replace(",", " ", $border_spec); // Why did we have this ?? rbg(10, 102, 10) > rgb(10 102 10) + $arr = explode(" ", $border_spec); + + // FIXME: handle partial values + + //For consistency of individal and combined properties, and with ie8 and firefox3 + //reset all attributes, even if only partially given + $this->_set_style_side_type('border',$side,'_style',self::$_defaults['border_'.$side.'_style'],$important); + $this->_set_style_side_type('border',$side,'_width',self::$_defaults['border_'.$side.'_width'],$important); + $this->_set_style_side_type('border',$side,'_color',self::$_defaults['border_'.$side.'_color'],$important); + + foreach ($arr as $value) { + $value = trim($value); + if ( in_array($value, self::$BORDER_STYLES) ) { + $this->_set_style_side_type('border',$side,'_style',$value,$important); + } + else if ( preg_match("/[.0-9]+(?:px|pt|pc|em|ex|%|in|mm|cm)|(?:thin|medium|thick)/", $value ) ) { + $this->_set_style_side_type('border',$side,'_width',$value,$important); + } + else { + // must be color + $this->_set_style_side_type('border',$side,'_color',$value,$important); + } + } + + //see __set and __get, on all assignments clear cache! + $this->_prop_cache['border_'.$side] = null; + $this->_props['border_'.$side] = $border_spec; + } + + /** + * Sets the border styles + * + * @link http://www.w3.org/TR/CSS21/box.html#border-properties + * @param string $val + */ + function set_border_top($val) { + $this->_set_border("top", $val, isset($this->_important_props['border_top'])); + } + + function set_border_right($val) { + $this->_set_border("right", $val, isset($this->_important_props['border_right'])); + } + + function set_border_bottom($val) { + $this->_set_border("bottom", $val, isset($this->_important_props['border_bottom'])); + } + + function set_border_left($val) { + $this->_set_border("left", $val, isset($this->_important_props['border_left'])); + } + + function set_border($val) { + $important = isset($this->_important_props["border"]); + $this->_set_border("top", $val, $important); + $this->_set_border("right", $val, $important); + $this->_set_border("bottom", $val, $important); + $this->_set_border("left", $val, $important); + //see __set and __get, on all assignments clear cache, not needed on direct set through __set + $this->_prop_cache["border"] = null; + $this->_props["border"] = $val; + } + + function set_border_width($val) { + $this->_set_style_type_important('border','_width',$val); + } + + function set_border_color($val) { + $this->_set_style_type_important('border','_color',$val); + } + + function set_border_style($val) { + $this->_set_style_type_important('border','_style',$val); + } + + /** + * Sets the border radius size + * + * http://www.w3.org/TR/css3-background/#corners + */ + function set_border_top_left_radius($val) { + $this->_set_border_radius_corner($val, "top_left"); + } + + function set_border_top_right_radius($val) { + $this->_set_border_radius_corner($val, "top_right"); + } + + function set_border_bottom_left_radius($val) { + $this->_set_border_radius_corner($val, "bottom_left"); + } + + function set_border_bottom_right_radius($val) { + $this->_set_border_radius_corner($val, "bottom_right"); + } + + function set_border_radius($val) { + $val = preg_replace("/\s*\,\s*/", ",", $val); // when border-radius has spaces + $arr = explode(" ", $val); + + switch (count($arr)) { + case 1: $this->_set_border_radii($arr[0],$arr[0],$arr[0],$arr[0]); break; + case 2: $this->_set_border_radii($arr[0],$arr[1],$arr[0],$arr[1]); break; + case 3: $this->_set_border_radii($arr[0],$arr[1],$arr[2],$arr[1]); break; + case 4: $this->_set_border_radii($arr[0],$arr[1],$arr[2],$arr[3]); break; + } + } + + protected function _set_border_radii($val1, $val2, $val3, $val4) { + $this->_set_border_radius_corner($val1, "top_left"); + $this->_set_border_radius_corner($val2, "top_right"); + $this->_set_border_radius_corner($val3, "bottom_right"); + $this->_set_border_radius_corner($val4, "bottom_left"); + } + + protected function _set_border_radius_corner($val, $corner) { + $this->_has_border_radius = true; + + //see __set and __get, on all assignments clear cache! + $this->_prop_cache["border_" . $corner . "_radius"] = null; + + $this->_props["border_" . $corner . "_radius"] = $this->length_in_pt($val); + } + + /** + * Sets the outline styles + * + * @link http://www.w3.org/TR/CSS21/ui.html#dynamic-outlines + * @param string $val + */ + function set_outline($val) { + $important = isset($this->_important_props["outline"]); + + $props = array( + "outline_style", + "outline_width", + "outline_color", + ); + + foreach($props as $prop) { + $_val = self::$_defaults[$prop]; + + if ( !isset($this->_important_props[$prop]) || $important) { + //see __set and __get, on all assignments clear cache! + $this->_prop_cache[$prop] = null; + if ( $important ) { + $this->_important_props[$prop] = true; + } + $this->_props[$prop] = $_val; + } + } + + $val = preg_replace("/\s*\,\s*/", ",", $val); // when rgb() has spaces + $arr = explode(" ", $val); + foreach ($arr as $value) { + $value = trim($value); + + if ( in_array($value, self::$BORDER_STYLES) ) { + $this->set_outline_style($value); + } + else if ( preg_match("/[.0-9]+(?:px|pt|pc|em|ex|%|in|mm|cm)|(?:thin|medium|thick)/", $value ) ) { + $this->set_outline_width($value); + } + else { + // must be color + $this->set_outline_color($value); + } + } + + //see __set and __get, on all assignments clear cache, not needed on direct set through __set + $this->_prop_cache["outline"] = null; + $this->_props["outline"] = $val; + } + + function set_outline_width($val) { + $this->_set_style_type_important('outline','_width',$val); + } + + function set_outline_color($val) { + $this->_set_style_type_important('outline','_color',$val); + } + + function set_outline_style($val) { + $this->_set_style_type_important('outline','_style',$val); + } + + /** + * Sets the border spacing + * + * @link http://www.w3.org/TR/CSS21/box.html#border-properties + * @param float $val + */ + function set_border_spacing($val) { + $arr = explode(" ", $val); + + if ( count($arr) == 1 ) { + $arr[1] = $arr[0]; + } + + //see __set and __get, on all assignments clear cache, not needed on direct set through __set + $this->_prop_cache["border_spacing"] = null; + $this->_props["border_spacing"] = "$arr[0] $arr[1]"; + } + + /** + * Sets the list style image + * + * @link http://www.w3.org/TR/CSS21/generate.html#propdef-list-style-image + * @param $val + */ + function set_list_style_image($val) { + //see __set and __get, on all assignments clear cache, not needed on direct set through __set + $this->_prop_cache["list_style_image"] = null; + $this->_props["list_style_image"] = $this->_image($val); + } + + /** + * Sets the list style + * + * @link http://www.w3.org/TR/CSS21/generate.html#propdef-list-style + * @param $val + */ + function set_list_style($val) { + $important = isset($this->_important_props["list_style"]); + $arr = explode(" ", str_replace(",", " ", $val)); + + static $types = array( + "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" + ); + + static $positions = array("inside", "outside"); + + foreach ($arr as $value) { + /* http://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 ( $value === "none" ) { + $this->_set_style("list_style_type", $value, $important); + $this->_set_style("list_style_image", $value, $important); + 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 explicite list_style_type) + //Internet Explorer 7/8 and dompdf is right. + + if ( mb_substr($value, 0, 3) === "url" ) { + $this->_set_style("list_style_image", $this->_image($value), $important); + continue; + } + + if ( in_array($value, $types) ) { + $this->_set_style("list_style_type", $value, $important); + } + else if ( in_array($value, $positions) ) { + $this->_set_style("list_style_position", $value, $important); + } + } + + //see __set and __get, on all assignments clear cache, not needed on direct set through __set + $this->_prop_cache["list_style"] = null; + $this->_props["list_style"] = $val; + } + + function set_size($val) { + $length_re = "/(\d+\s*(?:pt|px|pc|em|ex|in|cm|mm|%))/"; + + $val = mb_strtolower($val); + + if ( $val === "auto" ) { + return; + } + + $parts = preg_split("/\s+/", $val); + + $computed = array(); + if ( preg_match($length_re, $parts[0]) ) { + $computed[] = $this->length_in_pt($parts[0]); + + if ( isset($parts[1]) && preg_match($length_re, $parts[1]) ) { + $computed[] = $this->length_in_pt($parts[1]); + } + else { + $computed[] = $computed[0]; + } + } + elseif ( isset(CPDF_Adapter::$PAPER_SIZES[$parts[0]]) ) { + $computed = array_slice(CPDF_Adapter::$PAPER_SIZES[$parts[0]], 2, 2); + + if ( isset($parts[1]) && $parts[1] === "landscape" ) { + $computed = array_reverse($computed); + } + } + else { + return; + } + + $this->_props["size"] = $computed; + } + + /** + * Sets the CSS3 transform property + * + * @link http://www.w3.org/TR/css3-2d-transforms/#transform-property + * @param string $val + */ + function set_transform($val) { + $number = "\s*([^,\s]+)\s*"; + $tr_value = "\s*([^,\s]+)\s*"; + $angle = "\s*([^,\s]+(?:deg|rad)?)\s*"; + + if ( !preg_match_all("/[a-z]+\([^\)]+\)/i", $val, $parts, PREG_SET_ORDER) ) { + return; + } + + $functions = array( + //"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 = array(); + + 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) { + // units + case "rotate": + case "skew": + case "skewX": + case "skewY": + + foreach($values as $i => $value) { + if ( strpos($value, "rad") ) { + $values[$i] = rad2deg(floatval($value)); + } + else { + $values[$i] = floatval($value); + } + } + + switch($name) { + case "skew": + if ( !isset($values[1]) ) { + $values[1] = 0; + } + break; + case "skewX": + $name = "skew"; + $values = array($values[0], 0); + break; + case "skewY": + $name = "skew"; + $values = array(0, $values[0]); + break; + } + break; + + // units + case "translate": + $values[0] = $this->length_in_pt($values[0], $this->width); + + if ( isset($values[1]) ) { + $values[1] = $this->length_in_pt($values[1], $this->height); + } + else { + $values[1] = 0; + } + break; + + case "translateX": + $name = "translate"; + $values = array($this->length_in_pt($values[0], $this->width), 0); + break; + + case "translateY": + $name = "translate"; + $values = array(0, $this->length_in_pt($values[0], $this->height)); + break; + + // units + case "scale": + if ( !isset($values[1]) ) { + $values[1] = $values[0]; + } + break; + + case "scaleX": + $name = "scale"; + $values = array($values[0], 1.0); + break; + + case "scaleY": + $name = "scale"; + $values = array(1.0, $values[0]); + break; + } + + $transforms[] = array( + $name, + $values, + ); + } + } + } + + //see __set and __get, on all assignments clear cache, not needed on direct set through __set + $this->_prop_cache["transform"] = null; + $this->_props["transform"] = $transforms; + } + + function set__webkit_transform($val) { + $this->set_transform($val); + } + + function set__webkit_transform_origin($val) { + $this->set_transform_origin($val); + } + + /** + * Sets the CSS3 transform-origin property + * + * @link http://www.w3.org/TR/css3-2d-transforms/#transform-origin + * @param string $val + */ + function set_transform_origin($val) { + $values = preg_split("/\s+/", $val); + + if ( count($values) === 0) { + return; + } + + foreach($values as &$value) { + if ( in_array($value, array("top", "left")) ) { + $value = 0; + } + + if ( in_array($value, array("bottom", "right")) ) { + $value = "100%"; + } + } + + if ( !isset($values[1]) ) { + $values[1] = $values[0]; + } + + //see __set and __get, on all assignments clear cache, not needed on direct set through __set + $this->_prop_cache["transform_origin"] = null; + $this->_props["transform_origin"] = $values; + } + + protected function parse_image_resolution($val) { + // 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 + function set_background_image_resolution($val) { + $parsed = $this->parse_image_resolution($val); + + $this->_prop_cache["background_image_resolution"] = null; + $this->_props["background_image_resolution"] = $parsed; + } + + // auto | normal | dpi + function set_image_resolution($val) { + $parsed = $this->parse_image_resolution($val); + + $this->_prop_cache["image_resolution"] = null; + $this->_props["image_resolution"] = $parsed; + } + + function set__dompdf_background_image_resolution($val) { + $this->set_background_image_resolution($val); + } + + function set__dompdf_image_resolution($val) { + $this->set_image_resolution($val); + } + + function set_z_index($val) { + if ( round($val) != $val && $val !== "auto" ) { + return; + } + + $this->_prop_cache["z_index"] = null; + $this->_props["z_index"] = $val; + } + + function set_counter_increment($val) { + $val = trim($val); + $value = null; + + if ( in_array($val, array("none", "inherit")) ) { + $value = $val; + } + else { + if ( preg_match_all("/(".self::CSS_IDENTIFIER.")(?:\s+(".self::CSS_INTEGER."))?/", $val, $matches, PREG_SET_ORDER) ){ + $value = array(); + foreach($matches as $match) { + $value[$match[1]] = isset($match[2]) ? $match[2] : 1; + } + } + } + + $this->_prop_cache["counter_increment"] = null; + $this->_props["counter_increment"] = $value; + } + + /** + * 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*/ + function __toString() { + return print_r(array_merge(array("parent_font_size" => $this->_parent_font_size), + $this->_props), true); + } + +/*DEBUGCSS*/ function debug_print() { +/*DEBUGCSS*/ print "parent_font_size:".$this->_parent_font_size . ";\n"; +/*DEBUGCSS*/ foreach($this->_props as $prop => $val ) { +/*DEBUGCSS*/ print $prop.':'.$val; +/*DEBUGCSS*/ if (isset($this->_important_props[$prop])) { +/*DEBUGCSS*/ print '!important'; +/*DEBUGCSS*/ } +/*DEBUGCSS*/ print ";\n"; +/*DEBUGCSS*/ } +/*DEBUGCSS*/ } +} diff --git a/library/vendor/dompdf/include/stylesheet.cls.php b/library/vendor/dompdf/include/stylesheet.cls.php new file mode 100644 index 000000000..3b2ba3180 --- /dev/null +++ b/library/vendor/dompdf/include/stylesheet.cls.php @@ -0,0 +1,1418 @@ + + * @author Helmut Tischer + * @author Fabien Ménager + * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + */ + +/** + * The location of the default built-in CSS file. + * {@link Stylesheet::DEFAULT_STYLESHEET} + */ +define('__DEFAULT_STYLESHEET', DOMPDF_LIB_DIR . DIRECTORY_SEPARATOR . "res" . DIRECTORY_SEPARATOR . "html.css"); + +/** + * 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 = __DEFAULT_STYLESHEET; + + /** + * 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; + + private static $_stylesheet_origins = array( + self::ORIG_UA => -0x0FFFFFFF, // user agent style sheets + self::ORIG_USER => -0x0000FFFF, // user normal style sheets + self::ORIG_AUTHOR => 0x00000000, // author normal style sheets + ); + + /** + * 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