+ */
+ private $_blobs;
+
+ /**
+ * 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
+ $child = $child->nextSibling;
+ }
+ } else {
+ $css = $tag->nodeValue;
+ }
+
+ // Set the base path of the Stylesheet to that of the file being processed
+ $this->css->set_protocol($this->protocol);
+ $this->css->set_host($this->baseHost);
+ $this->css->set_base_path($this->basePath);
+
+ $this->css->load_css($css, Stylesheet::ORIG_AUTHOR);
+ break;
+ }
+
+ // Set the base path of the Stylesheet to that of the file being processed
+ $this->css->set_protocol($this->protocol);
+ $this->css->set_host($this->baseHost);
+ $this->css->set_base_path($this->basePath);
+ }
+ }
+
+ /**
+ * @param string $cacheId
+ * @deprecated
+ */
+ public function enable_caching($cacheId)
+ {
+ $this->enableCaching($cacheId);
+ }
+
+ /**
+ * Enable experimental caching capability
+ *
+ * @param string $cacheId
+ */
+ public function enableCaching($cacheId)
+ {
+ $this->cacheId = $cacheId;
+ }
+
+ /**
+ * @param string $value
+ * @return bool
+ * @deprecated
+ */
+ public function parse_default_view($value)
+ {
+ return $this->parseDefaultView($value);
+ }
+
+ /**
+ * @param string $value
+ * @return bool
+ */
+ public function parseDefaultView($value)
+ {
+ $valid = ["XYZ", "Fit", "FitH", "FitV", "FitR", "FitB", "FitBH", "FitBV"];
+
+ $options = preg_split("/\s*,\s*/", trim($value));
+ $defaultView = array_shift($options);
+
+ if (!in_array($defaultView, $valid)) {
+ return false;
+ }
+
+ $this->setDefaultView($defaultView, $options);
+ return true;
+ }
+
+ /**
+ * Renders the HTML to PDF
+ */
+ public function render()
+ {
+ $this->setPhpConfig();
+
+ $logOutputFile = $this->options->getLogOutputFile();
+ if ($logOutputFile) {
+ if (!file_exists($logOutputFile) && is_writable(dirname($logOutputFile))) {
+ touch($logOutputFile);
+ }
+
+ $startTime = microtime(true);
+ if (is_writable($logOutputFile)) {
+ ob_start();
+ }
+ }
+
+ $this->processHtml();
+
+ $this->css->apply_styles($this->tree);
+
+ // @page style rules : size, margins
+ $pageStyles = $this->css->get_page_styles();
+ $basePageStyle = $pageStyles["base"];
+ unset($pageStyles["base"]);
+
+ foreach ($pageStyles as $pageStyle) {
+ $pageStyle->inherit($basePageStyle);
+ }
+
+ // Set paper size if defined via CSS
+ if (is_array($basePageStyle->size)) {
+ // Orientation is already applied when reading the computed CSS
+ // `size` value. The `Canvas` back ends, however, unconditionally
+ // swap with an orientation of `landscape` and leave the defined
+ // size as-is with `portrait`; so passing `portrait` as orientation
+ // here (via the default value) is correct
+ [$width, $height] = $basePageStyle->size;
+ $this->setPaper([0, 0, $width, $height]);
+ }
+
+ // Create a new canvas instance if the current one does not match the
+ // desired paper size
+ $canvasWidth = $this->canvas->get_width();
+ $canvasHeight = $this->canvas->get_height();
+ $size = $this->getPaperSize();
+
+ if (
+ \Dompdf\Helpers::lengthEqual($canvasWidth, $size[2]) === false ||
+ \Dompdf\Helpers::lengthEqual($canvasHeight, $size[3]) === false
+ ) {
+ $this->canvas = CanvasFactory::get_instance($this, $this->paperSize, $this->paperOrientation);
+ $this->fontMetrics->setCanvas($this->canvas);
+ }
+
+ $canvas = $this->canvas;
+
+ $root_frame = $this->tree->get_root();
+ $root = Factory::decorate_root($root_frame, $this);
+ foreach ($this->tree as $frame) {
+ if ($frame === $root_frame) {
+ continue;
+ }
+ Factory::decorate_frame($frame, $this, $root);
+ }
+
+ // Add meta information
+ $title = $this->dom->getElementsByTagName("title");
+ if ($title->length) {
+ $canvas->add_info("Title", trim($title->item(0)->nodeValue));
+ }
+
+ $metas = $this->dom->getElementsByTagName("meta");
+ $labels = [
+ "author" => "Author",
+ "keywords" => "Keywords",
+ "description" => "Subject",
+ ];
+ /** @var \DOMElement $meta */
+ foreach ($metas as $meta) {
+ $name = mb_strtolower($meta->getAttribute("name"));
+ $value = trim($meta->getAttribute("content"));
+
+ if (isset($labels[$name])) {
+ $canvas->add_info($labels[$name], $value);
+ continue;
+ }
+
+ if ($name === "dompdf.view" && $this->parseDefaultView($value)) {
+ $canvas->set_default_view($this->defaultView, $this->defaultViewOptions);
+ }
+ }
+
+ $root->set_containing_block(0, 0, $canvas->get_width(), $canvas->get_height());
+ $root->set_renderer(new Renderer($this));
+
+ // This is where the magic happens:
+ $root->reflow();
+
+ if (isset($this->callbacks["end_document"])) {
+ $fs = $this->callbacks["end_document"];
+
+ foreach ($fs as $f) {
+ $canvas->page_script($f);
+ }
+ }
+
+ // Clean up cached images
+ if (!$this->options->getDebugKeepTemp()) {
+ Cache::clear($this->options->getDebugPng());
+ }
+
+ global $_dompdf_warnings, $_dompdf_show_warnings;
+ if ($_dompdf_show_warnings && isset($_dompdf_warnings)) {
+ echo 'Dompdf Warnings
';
+ foreach ($_dompdf_warnings as $msg) {
+ echo $msg . "\n";
+ }
+
+ if ($canvas instanceof CPDF) {
+ echo $canvas->get_cpdf()->messages;
+ }
+ echo '';
+ flush();
+ }
+
+ if ($logOutputFile && is_writable($logOutputFile)) {
+ $this->writeLog($logOutputFile, $startTime);
+ ob_end_clean();
+ }
+
+ $this->restorePhpConfig();
+ }
+
+ /**
+ * Writes the output buffer in the log file
+ *
+ * @param string $logOutputFile
+ * @param float $startTime
+ */
+ private function writeLog(string $logOutputFile, float $startTime): void
+ {
+ $frames = Frame::$ID_COUNTER;
+ $memory = memory_get_peak_usage(true) / 1024;
+ $time = (microtime(true) - $startTime) * 1000;
+
+ $out = sprintf(
+ "%6d" .
+ "%10.2f KB" .
+ "%10.2f ms" .
+ " " .
+ ($this->quirksmode ? " ON" : "OFF") .
+ "
", $frames, $memory, $time);
+
+ $out .= ob_get_contents();
+ ob_clean();
+
+ file_put_contents($logOutputFile, $out);
+ }
+
+ /**
+ * Add meta information to the PDF after rendering.
+ *
+ * @deprecated
+ */
+ public function add_info($label, $value)
+ {
+ $this->addInfo($label, $value);
+ }
+
+ /**
+ * Add meta information to the PDF after rendering.
+ *
+ * @param string $label Label of the value (Creator, Producer, etc.)
+ * @param string $value The text to set
+ */
+ public function addInfo(string $label, string $value): void
+ {
+ $this->canvas->add_info($label, $value);
+ }
+
+ /**
+ * Streams the PDF to the client.
+ *
+ * The file will open a download dialog by default. The options
+ * parameter controls the output. Accepted options (array keys) are:
+ *
+ * 'compress' = > 1 (=default) or 0:
+ * Apply content stream compression
+ *
+ * 'Attachment' => 1 (=default) or 0:
+ * Set the 'Content-Disposition:' HTTP header to 'attachment'
+ * (thereby causing the browser to open a download dialog)
+ *
+ * @param string $filename the name of the streamed file
+ * @param array $options header options (see above)
+ */
+ public function stream($filename = "document.pdf", $options = [])
+ {
+ $this->setPhpConfig();
+
+ $this->canvas->stream($filename, $options);
+
+ $this->restorePhpConfig();
+ }
+
+ /**
+ * Returns the PDF as a string.
+ *
+ * The options parameter controls the output. Accepted options are:
+ *
+ * 'compress' = > 1 or 0 - apply content stream compression, this is
+ * on (1) by default
+ *
+ * @param array $options options (see above)
+ *
+ * @return string
+ */
+ public function output($options = [])
+ {
+ $this->setPhpConfig();
+
+ $output = $this->canvas->output($options);
+
+ $this->restorePhpConfig();
+
+ return $output;
+ }
+
+ /**
+ * @return string
+ * @deprecated
+ */
+ public function output_html()
+ {
+ return $this->outputHtml();
+ }
+
+ /**
+ * Returns the underlying HTML document as a string
+ *
+ * @return string
+ */
+ public function outputHtml()
+ {
+ return $this->dom->saveHTML();
+ }
+
+ /**
+ * Get the dompdf option value
+ *
+ * @param string $key
+ * @return mixed
+ * @deprecated
+ */
+ public function get_option($key)
+ {
+ return $this->options->get($key);
+ }
+
+ /**
+ * @param string $key
+ * @param mixed $value
+ * @return $this
+ * @deprecated
+ */
+ public function set_option($key, $value)
+ {
+ $new_options = clone $this->options;
+ $new_options->set($key, $value);
+ $this->setOptions($new_options);
+ return $this;
+ }
+
+ /**
+ * @param array $options
+ * @return $this
+ * @deprecated
+ */
+ public function set_options(array $options)
+ {
+ $new_options = clone $this->options;
+ $new_options->set($options);
+ $this->setOptions($new_options);
+ return $this;
+ }
+
+ /**
+ * @param string $size
+ * @param string $orientation
+ * @deprecated
+ */
+ public function set_paper($size, $orientation = "portrait")
+ {
+ $this->setPaper($size, $orientation);
+ }
+
+ /**
+ * Sets the paper size & orientation
+ *
+ * @param string|float[] $size 'letter', 'legal', 'A4', etc. {@link Dompdf\Adapter\CPDF::$PAPER_SIZES}
+ * @param string $orientation 'portrait' or 'landscape'
+ * @return $this
+ */
+ public function setPaper($size, string $orientation = "portrait"): self
+ {
+ $current_size = $this->getPaperSize();
+ $this->paperSize = $size;
+ $this->paperOrientation = $orientation;
+ $new_size = $this->getPaperSize();
+ if (
+ \Dompdf\Helpers::lengthEqual($current_size[2], $new_size[2]) === false ||
+ \Dompdf\Helpers::lengthEqual($current_size[3], $new_size[3]) === false
+ ) {
+ $this->canvas = CanvasFactory::get_instance($this, $this->paperSize, $this->paperOrientation);
+ }
+ return $this;
+ }
+
+ /**
+ * Gets the paper size
+ *
+ * @return float[] A four-element float array
+ */
+ public function getPaperSize(): array
+ {
+ $paper = $this->paperSize;
+ $orientation = $this->paperOrientation;
+
+ if (is_array($paper)) {
+ $size = array_map("floatval", $paper);
+ } else {
+ $paper = strtolower($paper);
+ $size = CPDF::$PAPER_SIZES[$paper] ?? CPDF::$PAPER_SIZES["letter"];
+ }
+
+ if (strtolower($orientation) === "landscape") {
+ [$size[2], $size[3]] = [$size[3], $size[2]];
+ }
+
+ return $size;
+ }
+
+ /**
+ * Gets the paper orientation
+ *
+ * @return string Either "portrait" or "landscape"
+ */
+ public function getPaperOrientation(): string
+ {
+ return $this->paperOrientation;
+ }
+
+ /**
+ * @param FrameTree $tree
+ * @return $this
+ */
+ public function setTree(FrameTree $tree)
+ {
+ $this->tree = $tree;
+ return $this;
+ }
+
+ /**
+ * @return FrameTree
+ * @deprecated
+ */
+ public function get_tree()
+ {
+ return $this->getTree();
+ }
+
+ /**
+ * Returns the underlying {@link FrameTree} object
+ *
+ * @return FrameTree
+ */
+ public function getTree()
+ {
+ return $this->tree;
+ }
+
+ /**
+ * @param string $protocol
+ * @return $this
+ * @deprecated
+ */
+ public function set_protocol($protocol)
+ {
+ return $this->setProtocol($protocol);
+ }
+
+ /**
+ * Sets the protocol to use
+ * FIXME validate these
+ *
+ * @param string $protocol
+ * @return $this
+ */
+ public function setProtocol(string $protocol)
+ {
+ $this->protocol = $protocol;
+ return $this;
+ }
+
+ /**
+ * @return string
+ * @deprecated
+ */
+ public function get_protocol()
+ {
+ return $this->getProtocol();
+ }
+
+ /**
+ * Returns the protocol in use
+ *
+ * @return string
+ */
+ public function getProtocol()
+ {
+ return $this->protocol;
+ }
+
+ /**
+ * @param string $host
+ * @deprecated
+ */
+ public function set_host($host)
+ {
+ $this->setBaseHost($host);
+ }
+
+ /**
+ * Sets the base hostname
+ *
+ * @param string $baseHost
+ * @return $this
+ */
+ public function setBaseHost(string $baseHost)
+ {
+ $this->baseHost = $baseHost;
+ return $this;
+ }
+
+ /**
+ * @return string
+ * @deprecated
+ */
+ public function get_host()
+ {
+ return $this->getBaseHost();
+ }
+
+ /**
+ * Returns the base hostname
+ *
+ * @return string
+ */
+ public function getBaseHost()
+ {
+ return $this->baseHost;
+ }
+
+ /**
+ * Sets the base path
+ *
+ * @param string $path
+ * @deprecated
+ */
+ public function set_base_path($path)
+ {
+ $this->setBasePath($path);
+ }
+
+ /**
+ * Sets the base path
+ *
+ * @param string $basePath
+ * @return $this
+ */
+ public function setBasePath(string $basePath)
+ {
+ $this->basePath = $basePath;
+ return $this;
+ }
+
+ /**
+ * @return string
+ * @deprecated
+ */
+ public function get_base_path()
+ {
+ return $this->getBasePath();
+ }
+
+ /**
+ * Returns the base path
+ *
+ * @return string
+ */
+ public function getBasePath()
+ {
+ return $this->basePath;
+ }
+
+ /**
+ * @param string $default_view The default document view
+ * @param array $options The view's options
+ * @return $this
+ * @deprecated
+ */
+ public function set_default_view($default_view, $options)
+ {
+ return $this->setDefaultView($default_view, $options);
+ }
+
+ /**
+ * Sets the default view
+ *
+ * @param string $defaultView The default document view
+ * @param array $options The view's options
+ * @return $this
+ */
+ public function setDefaultView($defaultView, $options)
+ {
+ $this->defaultView = $defaultView;
+ $this->defaultViewOptions = $options;
+ return $this;
+ }
+
+ /**
+ * @param resource $http_context
+ * @return $this
+ * @deprecated
+ */
+ public function set_http_context($http_context)
+ {
+ return $this->setHttpContext($http_context);
+ }
+
+ /**
+ * Sets the HTTP context
+ *
+ * @param resource|array $httpContext
+ * @return $this
+ */
+ public function setHttpContext($httpContext)
+ {
+ $this->options->setHttpContext($httpContext);
+ return $this;
+ }
+
+ /**
+ * @return resource
+ * @deprecated
+ */
+ public function get_http_context()
+ {
+ return $this->getHttpContext();
+ }
+
+ /**
+ * Returns the HTTP context
+ *
+ * @return resource
+ */
+ public function getHttpContext()
+ {
+ return $this->options->getHttpContext();
+ }
+
+ /**
+ * Set a custom `Canvas` instance to render the document to.
+ *
+ * Be aware that the instance will be replaced on render if the document
+ * defines a paper size different from the canvas.
+ *
+ * @param Canvas $canvas
+ * @return $this
+ */
+ public function setCanvas(Canvas $canvas)
+ {
+ $this->canvas = $canvas;
+ $canvasWidth = $this->canvas->get_width();
+ $canvasHeight = $this->canvas->get_height();
+ $this->paperSize = [0, 0, $canvasWidth, $canvasHeight];
+ $this->paperOrientation = "portrait";
+ return $this;
+ }
+
+ /**
+ * @return Canvas
+ * @deprecated
+ */
+ public function get_canvas()
+ {
+ return $this->getCanvas();
+ }
+
+ /**
+ * Return the underlying Canvas instance (e.g. Dompdf\Adapter\CPDF, Dompdf\Adapter\GD)
+ *
+ * @return Canvas
+ */
+ public function getCanvas()
+ {
+ return $this->canvas;
+ }
+
+ /**
+ * @param Stylesheet $css
+ * @return $this
+ */
+ public function setCss(Stylesheet $css)
+ {
+ $this->css = $css;
+ return $this;
+ }
+
+ /**
+ * @return Stylesheet
+ * @deprecated
+ */
+ public function get_css()
+ {
+ return $this->getCss();
+ }
+
+ /**
+ * Returns the stylesheet
+ *
+ * @return Stylesheet
+ */
+ public function getCss()
+ {
+ return $this->css;
+ }
+
+ /**
+ * @param DOMDocument $dom
+ * @return $this
+ */
+ public function setDom(DOMDocument $dom)
+ {
+ $this->dom = $dom;
+ return $this;
+ }
+
+ /**
+ * @return DOMDocument
+ * @deprecated
+ */
+ public function get_dom()
+ {
+ return $this->getDom();
+ }
+
+ /**
+ * @return DOMDocument
+ */
+ public function getDom()
+ {
+ return $this->dom;
+ }
+
+ /**
+ * @param Options $options
+ * @return $this
+ */
+ public function setOptions(Options $options)
+ {
+ // For backwards compatibility
+ if ($this->options && $this->options->getHttpContext() && !$options->getHttpContext()) {
+ $options->setHttpContext($this->options->getHttpContext());
+ }
+
+ $this->options = $options;
+
+ $fontMetrics = $this->fontMetrics;
+ if (isset($fontMetrics)) {
+ $fontMetrics->setOptions($options);
+ }
+
+ if (isset($this->canvas)) {
+ $this->canvas = CanvasFactory::get_instance($this, $this->paperSize, $this->paperOrientation);
+ if (isset($fontMetrics)) {
+ $this->fontMetrics = new FontMetrics($this->canvas, $this->options);
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * @return Options
+ */
+ public function getOptions()
+ {
+ return $this->options;
+ }
+
+ /**
+ * @return array
+ * @deprecated
+ */
+ public function get_callbacks()
+ {
+ return $this->getCallbacks();
+ }
+
+ /**
+ * Returns the callbacks array
+ *
+ * @return array
+ */
+ public function getCallbacks()
+ {
+ return $this->callbacks;
+ }
+
+ /**
+ * @param array $callbacks the set of callbacks to set
+ * @return $this
+ * @deprecated
+ */
+ public function set_callbacks($callbacks)
+ {
+ return $this->setCallbacks($callbacks);
+ }
+
+ /**
+ * Define callbacks that allow modifying the document during render.
+ *
+ * The callbacks array should contain arrays with `event` set to a callback
+ * event name and `f` set to a function or any other callable.
+ *
+ * The available callback events are:
+ * * `begin_page_reflow`: called before page reflow
+ * * `begin_frame`: called before a frame is rendered
+ * * `end_frame`: called after frame rendering is complete
+ * * `begin_page_render`: called before a page is rendered
+ * * `end_page_render`: called after page rendering is complete
+ * * `end_document`: called for every page after rendering is complete
+ *
+ * The function `f` receives three arguments `Frame $frame`, `Canvas $canvas`,
+ * and `FontMetrics $fontMetrics` for all events but `end_document`. For
+ * `end_document`, the function receives four arguments `int $pageNumber`,
+ * `int $pageCount`, `Canvas $canvas`, and `FontMetrics $fontMetrics` instead.
+ *
+ * @param array $callbacks The set of callbacks to set.
+ * @return $this
+ */
+ public function setCallbacks(array $callbacks): self
+ {
+ $this->callbacks = [];
+
+ foreach ($callbacks as $c) {
+ if (is_array($c) && isset($c["event"]) && isset($c["f"])) {
+ $event = $c["event"];
+ $f = $c["f"];
+ if (is_string($event) && is_callable($f)) {
+ $this->callbacks[$event][] = $f;
+ }
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * @return boolean
+ * @deprecated
+ */
+ public function get_quirksmode()
+ {
+ return $this->getQuirksmode();
+ }
+
+ /**
+ * Get the quirks mode
+ *
+ * @return boolean true if quirks mode is active
+ */
+ public function getQuirksmode()
+ {
+ return $this->quirksmode;
+ }
+
+ /**
+ * @param FontMetrics $fontMetrics
+ * @return $this
+ */
+ public function setFontMetrics(FontMetrics $fontMetrics)
+ {
+ $this->fontMetrics = $fontMetrics;
+ return $this;
+ }
+
+ /**
+ * @return FontMetrics
+ */
+ public function getFontMetrics()
+ {
+ return $this->fontMetrics;
+ }
+
+ /**
+ * PHP5 overloaded getter
+ * Along with {@link Dompdf::__set()} __get() provides access to all
+ * properties directly. Typically __get() is not called directly outside
+ * of this class.
+ *
+ * @param string $prop
+ *
+ * @throws Exception
+ * @return mixed
+ */
+ function __get($prop)
+ {
+ switch ($prop) {
+ case 'version':
+ return $this->version;
+ default:
+ throw new Exception('Invalid property: ' . $prop);
+ }
+ }
+}
diff --git a/app/libs/dompdf/vendor/dompdf/dompdf/src/Exception.php b/app/libs/dompdf/vendor/dompdf/dompdf/src/Exception.php
new file mode 100644
index 0000000..3a90e47
--- /dev/null
+++ b/app/libs/dompdf/vendor/dompdf/dompdf/src/Exception.php
@@ -0,0 +1,27 @@
+setCanvas($canvas);
+ $this->setOptions($options);
+ $this->loadFontFamilies();
+ }
+
+ /**
+ * @deprecated
+ */
+ public function save_font_families()
+ {
+ $this->saveFontFamilies();
+ }
+
+ /**
+ * Saves the stored font family cache
+ *
+ * The name and location of the cache file are determined by {@link
+ * FontMetrics::USER_FONTS_FILE}. This file should be writable by the
+ * webserver process.
+ *
+ * @see FontMetrics::loadFontFamilies()
+ */
+ public function saveFontFamilies()
+ {
+ file_put_contents($this->getUserFontsFilePath(), json_encode($this->userFonts, JSON_PRETTY_PRINT));
+ }
+
+ /**
+ * @deprecated
+ */
+ public function load_font_families()
+ {
+ $this->loadFontFamilies();
+ }
+
+ /**
+ * Loads the stored font family cache
+ *
+ * @see FontMetrics::saveFontFamilies()
+ */
+ public function loadFontFamilies()
+ {
+ $file = $this->options->getRootDir() . "/lib/fonts/installed-fonts.dist.json";
+ $this->bundledFonts = json_decode(file_get_contents($file), true);
+
+ if (is_readable($this->getUserFontsFilePath())) {
+ $this->userFonts = json_decode(file_get_contents($this->getUserFontsFilePath()), true);
+ } else {
+ $this->loadFontFamiliesLegacy();
+ }
+ }
+
+ private function loadFontFamiliesLegacy()
+ {
+ $legacyCacheFile = $this->options->getFontDir() . '/dompdf_font_family_cache.php';
+ if (is_readable($legacyCacheFile)) {
+ $fontDir = $this->options->getFontDir();
+ $rootDir = $this->options->getRootDir();
+
+ $cacheDataClosure = require $legacyCacheFile;
+ $cacheData = is_array($cacheDataClosure) ? $cacheDataClosure : $cacheDataClosure($fontDir, $rootDir);
+ if (is_array($cacheData)) {
+ foreach ($cacheData as $family => $variants) {
+ if (!isset($this->bundledFonts[$family]) && is_array($variants)) {
+ foreach ($variants as $variant => $variantPath) {
+ $variantName = basename($variantPath);
+ $variantDir = dirname($variantPath);
+ if ($variantDir == $fontDir) {
+ $this->userFonts[$family][$variant] = $variantName;
+ } else {
+ $this->userFonts[$family][$variant] = $variantPath;
+ }
+ }
+ }
+ }
+ $this->saveFontFamilies();
+ }
+ }
+ }
+
+ /**
+ * @param array $style
+ * @param string $remote_file
+ * @param resource $context
+ * @return bool
+ * @deprecated
+ */
+ public function register_font($style, $remote_file, $context = null)
+ {
+ return $this->registerFont($style, $remote_file);
+ }
+
+ /**
+ * @param array $style
+ * @param string $remoteFile
+ * @param resource $context
+ * @return bool
+ */
+ public function registerFont($style, $remoteFile, $context = null)
+ {
+ $fontname = mb_strtolower($style["family"], "UTF-8");
+ $families = $this->getFontFamilies();
+
+ $entry = [];
+ if (isset($families[$fontname])) {
+ $entry = $families[$fontname];
+ }
+
+ $styleString = $this->getType("{$style['weight']} {$style['style']}");
+
+ $remoteHash = md5($remoteFile);
+
+ $prefix = $fontname . "_" . $styleString;
+ $prefix = trim($prefix, "-");
+ if (function_exists('iconv')) {
+ $prefix = @iconv('utf-8', 'us-ascii//TRANSLIT', $prefix);
+ }
+ $prefix_encoding = mb_detect_encoding($prefix, mb_detect_order(), true);
+ $substchar = mb_substitute_character();
+ mb_substitute_character(0x005F);
+ $prefix = mb_convert_encoding($prefix, "ISO-8859-1", $prefix_encoding);
+ mb_substitute_character($substchar);
+ $prefix = preg_replace("[\W]", "_", $prefix);
+ $prefix = preg_replace("/[^-_\w]+/", "", $prefix);
+
+ $localFile = $prefix . "_" . $remoteHash;
+ $localFilePath = $this->getOptions()->getFontDir() . "/" . $localFile;
+
+ if (isset($entry[$styleString]) && $localFilePath == $entry[$styleString]) {
+ return true;
+ }
+
+
+ $entry[$styleString] = $localFile;
+
+ // Download the remote file
+ [$protocol] = Helpers::explode_url($remoteFile);
+ $allowed_protocols = $this->options->getAllowedProtocols();
+ if (!array_key_exists($protocol, $allowed_protocols)) {
+ Helpers::record_warnings(E_USER_WARNING, "Permission denied on $remoteFile. The communication protocol is not supported.", __FILE__, __LINE__);
+ return false;
+ }
+
+ foreach ($allowed_protocols[$protocol]["rules"] as $rule) {
+ [$result, $message] = $rule($remoteFile);
+ if ($result !== true) {
+ Helpers::record_warnings(E_USER_WARNING, "Error loading $remoteFile: $message", __FILE__, __LINE__);
+ return false;
+ }
+ }
+
+ [$remoteFileContent, $http_response_header] = @Helpers::getFileContent($remoteFile, $context);
+ if ($remoteFileContent === null) {
+ return false;
+ }
+
+ $localTempFile = @tempnam($this->options->get("tempDir"), "dompdf-font-");
+ file_put_contents($localTempFile, $remoteFileContent);
+
+ $font = Font::load($localTempFile);
+
+ if (!$font) {
+ unlink($localTempFile);
+ return false;
+ }
+
+ $font->parse();
+ $font->saveAdobeFontMetrics("$localFilePath.ufm");
+ $font->close();
+
+ unlink($localTempFile);
+
+ if ( !file_exists("$localFilePath.ufm") ) {
+ return false;
+ }
+
+ $fontExtension = ".ttf";
+ switch ($font->getFontType()) {
+ case "TrueType":
+ default:
+ $fontExtension = ".ttf";
+ break;
+ }
+
+ // Save the changes
+ file_put_contents($localFilePath.$fontExtension, $remoteFileContent);
+
+ if ( !file_exists($localFilePath.$fontExtension) ) {
+ unlink("$localFilePath.ufm");
+ return false;
+ }
+
+ $this->setFontFamily($fontname, $entry);
+
+ return true;
+ }
+
+ /**
+ * @param $text
+ * @param $font
+ * @param $size
+ * @param float $word_spacing
+ * @param float $char_spacing
+ * @return float
+ * @deprecated
+ */
+ public function get_text_width($text, $font, $size, $word_spacing = 0.0, $char_spacing = 0.0)
+ {
+ //return self::$_pdf->get_text_width($text, $font, $size, $word_spacing, $char_spacing);
+ return $this->getTextWidth($text, $font, $size, $word_spacing, $char_spacing);
+ }
+
+ /**
+ * Calculates text size, in points
+ *
+ * @param string $text The text to be sized
+ * @param string $font The font file to use
+ * @param float $size The font size, in points
+ * @param float $wordSpacing Word spacing, if any
+ * @param float $charSpacing Char spacing, if any
+ *
+ * @return float
+ */
+ public function getTextWidth(string $text, $font, float $size, float $wordSpacing = 0.0, float $charSpacing = 0.0): float
+ {
+ // @todo Make sure this cache is efficient before enabling it
+ static $cache = [];
+
+ if ($text === "") {
+ return 0;
+ }
+
+ // Don't cache long strings
+ $useCache = !isset($text[50]); // Faster than strlen
+
+ // Text-size calculations depend on the canvas used. Make sure to not
+ // return wrong values when switching canvas backends
+ $canvasClass = get_class($this->canvas);
+ $key = "$canvasClass/$font/$size/$wordSpacing/$charSpacing";
+
+ if ($useCache && isset($cache[$key][$text])) {
+ return $cache[$key][$text];
+ }
+
+ $width = $this->canvas->get_text_width($text, $font, $size, $wordSpacing, $charSpacing);
+
+ if ($useCache) {
+ $cache[$key][$text] = $width;
+ }
+
+ return $width;
+ }
+
+ /**
+ * Maps substrings of text against the provided font list. This is achieved by
+ * parsing each character of the string against the supported glyphs for each
+ * font. Fonts preference is based on the order of the font list.
+ *
+ * Returns an array containing substring information that indicates the
+ * matched font (if any), start index, substring length, and (optionally)
+ * the actual text of the substring.
+ *
+ * @param string $text The text to map
+ * @param array $fontFamilies List of font families to map against
+ * @param string $subtype The font subtype (italic, bold, etc.)
+ * @param int $count The number of matches to return
+ * @param bool $returnSubstring Should the actual matched text be returned
+ * @return array
+ */
+ public function mapTextToFonts(string $text, array $fontFamilies, string $subtype = "normal", int $count = -1, bool $returnSubstring = false): array
+ {
+ $char_mapping = [];
+ $fonts = [];
+
+ foreach ($fontFamilies as $family) {
+ $font = $this->getFont($family, $subtype);
+ if ($font !== null) {
+ $fonts[] = $font;
+ }
+ }
+
+ if (function_exists("mb_str_split")) {
+ $char_array = mb_str_split($text, 1, "UTF-8");
+ } else {
+ $char_array = preg_split("//u", $text, -1, PREG_SPLIT_NO_EMPTY);
+ }
+ $start_index = 0;
+ $char_index = -1;
+ while (isset($char_array[++$char_index])) {
+ $char = $char_array[$char_index];
+ if (preg_match('/[\x00-\x1F\x7F]/u', $char)) {
+ //non-printable, moving on
+ continue;
+ }
+ $mapped_font = null;
+ foreach ($fonts as $font) {
+ if ($this->canvas->font_supports_char($font, $char)) {
+ $mapped_font = $font;
+ break;
+ }
+ }
+
+ if (!isset($char_mapping[$start_index])) {
+ $char_mapping[$start_index] = ["font" => $mapped_font, "length" => 0, "text" => null];
+ }
+
+ if ($mapped_font !== $char_mapping[$start_index]["font"]) {
+ $char_mapping[$start_index]["length"] = $char_index - $start_index;
+ if ($count > 0 && count($char_mapping) === $count) {
+ break;
+ }
+ $start_index = $char_index;
+ $char_mapping[$start_index] = ["font" => $mapped_font, "length" => 0, "text" => null];
+ }
+ }
+
+ if ($returnSubstring) {
+ // build the string for each mapping
+ foreach ($char_mapping as $start_index => &$info) {
+ $info["text"] = mb_substr($text, $start_index, $info["length"], "UTF-8");
+ }
+ }
+
+ return $char_mapping;
+ }
+
+ /**
+ * @param $font
+ * @param $size
+ * @return float
+ * @deprecated
+ */
+ public function get_font_height($font, $size)
+ {
+ return $this->getFontHeight($font, $size);
+ }
+
+ /**
+ * Calculates font height, in points
+ *
+ * @param string $font The font file to use
+ * @param float $size The font size, in points
+ *
+ * @return float
+ */
+ public function getFontHeight($font, float $size): float
+ {
+ return $this->canvas->get_font_height($font, $size);
+ }
+
+ /**
+ * Calculates font baseline, in points
+ *
+ * @param string $font The font file to use
+ * @param float $size The font size, in points
+ *
+ * @return float
+ */
+ public function getFontBaseline($font, float $size): float
+ {
+ return $this->canvas->get_font_baseline($font, $size);
+ }
+
+ /**
+ * @param $family_raw
+ * @param string $subtype_raw
+ * @return string
+ * @deprecated
+ */
+ public function get_font($family_raw, $subtype_raw = "normal")
+ {
+ return $this->getFont($family_raw, $subtype_raw);
+ }
+
+ /**
+ * Resolves a font family & subtype into an actual font file
+ * Subtype can be one of 'normal', 'bold', 'italic' or 'bold_italic'. If
+ * the particular font family has no suitable font file, the default font
+ * ({@link Options::defaultFont}) is used. The font file returned
+ * is the absolute pathname to the font file on the system.
+ *
+ * @param string|null $familyRaw
+ * @param string $subtypeRaw
+ *
+ * @return string|null
+ */
+ public function getFont($familyRaw, $subtypeRaw = "normal")
+ {
+ static $cache = [];
+
+ if (!$familyRaw) {
+ $familyRaw = $familyRaw === null ? 0 : $this->options->getDefaultFont();
+ }
+ if (!$subtypeRaw) {
+ $subtypeRaw = "normal";
+ }
+
+ if (isset($cache[$familyRaw][$subtypeRaw])) {
+ return $cache[$familyRaw][$subtypeRaw];
+ }
+
+ /* Allow calling for various fonts in search path. Therefore not immediately
+ * return replacement on non match.
+ * Only when called with NULL try replacement.
+ * When this is also missing there is really trouble.
+ * If only the subtype fails, nevertheless return failure.
+ * Only on checking the fallback font, check various subtypes on same font.
+ */
+
+ $subtype = strtolower($subtypeRaw);
+
+ $families = $this->getFontFamilies();
+ if ($familyRaw) {
+ $family = str_replace(["'", '"'], "", strtolower($familyRaw));
+
+ if (isset($families[$family][$subtype])) {
+ return $cache[$familyRaw][$subtypeRaw] = $families[$family][$subtype];
+ }
+
+ return null;
+ }
+
+ $fallback_families = [strtolower($this->options->getDefaultFont()), "serif"];
+ foreach ($fallback_families as $family) {
+ if (isset($families[$family][$subtype])) {
+ return $cache[$familyRaw][$subtypeRaw] = $families[$family][$subtype];
+ }
+
+ if (!isset($families[$family])) {
+ continue;
+ }
+
+ $family = $families[$family];
+
+ foreach ($family as $sub => $font) {
+ if (strpos($subtype, $sub) !== false) {
+ return $cache[$familyRaw][$subtypeRaw] = $font;
+ }
+ }
+
+ if ($subtype !== "normal") {
+ foreach ($family as $sub => $font) {
+ if ($sub !== "normal") {
+ return $cache[$familyRaw][$subtypeRaw] = $font;
+ }
+ }
+ }
+
+ $subtype = "normal";
+
+ if (isset($family[$subtype])) {
+ return $cache[$familyRaw][$subtypeRaw] = $family[$subtype];
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * @param $family
+ * @return null|string
+ * @deprecated
+ */
+ public function get_family($family)
+ {
+ return $this->getFamily($family);
+ }
+
+ /**
+ * @param string $family
+ * @return null|string
+ */
+ public function getFamily($family)
+ {
+ $family = str_replace(["'", '"'], "", mb_strtolower($family, "UTF-8"));
+ $families = $this->getFontFamilies();
+
+ if (isset($families[$family])) {
+ return $families[$family];
+ }
+
+ return null;
+ }
+
+ /**
+ * @param $type
+ * @return string
+ * @deprecated
+ */
+ public function get_type($type)
+ {
+ return $this->getType($type);
+ }
+
+ /**
+ * @param string $type
+ * @return string
+ */
+ public function getType($type)
+ {
+ if (preg_match('/bold/i', $type)) {
+ $weight = 700;
+ } elseif (preg_match('/([1-9]00)/', $type, $match)) {
+ $weight = (int)$match[0];
+ } else {
+ $weight = 400;
+ }
+ $weight = $weight === 400 ? 'normal' : $weight;
+ $weight = $weight === 700 ? 'bold' : $weight;
+
+ $style = preg_match('/italic|oblique/i', $type) ? 'italic' : null;
+
+ if ($weight === 'normal' && $style !== null) {
+ return $style;
+ }
+
+ return $style === null
+ ? $weight
+ : $weight.'_'.$style;
+ }
+
+ /**
+ * @return array
+ * @deprecated
+ */
+ public function get_font_families()
+ {
+ return $this->getFontFamilies();
+ }
+
+ /**
+ * Returns the current font lookup table
+ *
+ * @return array
+ */
+ public function getFontFamilies()
+ {
+ if (!isset($this->fontFamilies)) {
+ $this->setFontFamilies();
+ }
+ return $this->fontFamilies;
+ }
+
+ /**
+ * Convert loaded fonts to font lookup table
+ *
+ * @return array
+ */
+ public function setFontFamilies()
+ {
+ $fontFamilies = [];
+ if (isset($this->bundledFonts) && is_array($this->bundledFonts)) {
+ foreach ($this->bundledFonts as $family => $variants) {
+ if (!isset($fontFamilies[$family])) {
+ $fontFamilies[$family] = array_map(function ($variant) {
+ return $this->getOptions()->getRootDir() . '/lib/fonts/' . $variant;
+ }, $variants);
+ }
+ }
+ }
+ if (isset($this->userFonts) && is_array($this->userFonts)) {
+ foreach ($this->userFonts as $family => $variants) {
+ $fontFamilies[$family] = array_map(function ($variant) {
+ $variantName = basename($variant);
+ if ($variantName === $variant) {
+ return $this->getOptions()->getFontDir() . '/' . $variant;
+ }
+ return $variant;
+ }, $variants);
+ }
+ }
+ $this->fontFamilies = $fontFamilies;
+ }
+
+ /**
+ * @param string $fontname
+ * @param mixed $entry
+ * @deprecated
+ */
+ public function set_font_family($fontname, $entry)
+ {
+ $this->setFontFamily($fontname, $entry);
+ }
+
+ /**
+ * @param string $fontname
+ * @param mixed $entry
+ */
+ public function setFontFamily($fontname, $entry)
+ {
+ $this->userFonts[mb_strtolower($fontname, "UTF-8")] = $entry;
+ $this->saveFontFamilies();
+ unset($this->fontFamilies);
+ }
+
+ /**
+ * @return string
+ */
+ public function getUserFontsFilePath()
+ {
+ return $this->options->getFontDir() . '/' . self::USER_FONTS_FILE;
+ }
+
+ /**
+ * @param Options $options
+ * @return $this
+ */
+ public function setOptions(Options $options)
+ {
+ $this->options = $options;
+ unset($this->fontFamilies);
+ return $this;
+ }
+
+ /**
+ * @return Options
+ */
+ public function getOptions()
+ {
+ return $this->options;
+ }
+
+ /**
+ * @param Canvas $canvas
+ * @return $this
+ */
+ public function setCanvas(Canvas $canvas)
+ {
+ $this->canvas = $canvas;
+ return $this;
+ }
+
+ /**
+ * @return Canvas
+ */
+ public function getCanvas()
+ {
+ return $this->canvas;
+ }
+}
diff --git a/app/libs/dompdf/vendor/dompdf/dompdf/src/Frame.php b/app/libs/dompdf/vendor/dompdf/dompdf/src/Frame.php
new file mode 100644
index 0000000..678fb65
--- /dev/null
+++ b/app/libs/dompdf/vendor/dompdf/dompdf/src/Frame.php
@@ -0,0 +1,1238 @@
+_node = $node;
+
+ $this->_parent = null;
+ $this->_first_child = null;
+ $this->_last_child = null;
+ $this->_prev_sibling = $this->_next_sibling = null;
+
+ $this->_style = null;
+
+ $this->_containing_block = [
+ "x" => null,
+ "y" => null,
+ "w" => null,
+ "h" => null,
+ ];
+
+ $this->_containing_block[0] =& $this->_containing_block["x"];
+ $this->_containing_block[1] =& $this->_containing_block["y"];
+ $this->_containing_block[2] =& $this->_containing_block["w"];
+ $this->_containing_block[3] =& $this->_containing_block["h"];
+
+ $this->_position = [
+ "x" => null,
+ "y" => null,
+ ];
+
+ $this->_position[0] =& $this->_position["x"];
+ $this->_position[1] =& $this->_position["y"];
+
+ $this->_opacity = 1.0;
+ $this->_decorator = null;
+
+ $this->set_id(self::$ID_COUNTER++);
+ }
+
+ /**
+ * WIP : preprocessing to remove all the unused whitespace
+ */
+ protected function ws_trim()
+ {
+ if ($this->ws_keep()) {
+ return;
+ }
+
+ if (self::$_ws_state === self::WS_SPACE) {
+ $node = $this->_node;
+
+ if ($node->nodeName === "#text" && !empty($node->nodeValue)) {
+ $node->nodeValue = preg_replace("/[ \t\r\n\f]+/u", " ", trim($node->nodeValue));
+ self::$_ws_state = self::WS_TEXT;
+ }
+ }
+ }
+
+ /**
+ * @return bool
+ */
+ protected function ws_keep()
+ {
+ $whitespace = $this->get_style()->white_space;
+
+ return in_array($whitespace, ["pre", "pre-wrap", "pre-line"]);
+ }
+
+ /**
+ * @return bool
+ */
+ protected function ws_is_text()
+ {
+ $node = $this->get_node();
+
+ if ($node->nodeName === "img") {
+ return true;
+ }
+
+ if (!$this->is_in_flow()) {
+ return false;
+ }
+
+ if ($this->is_text_node()) {
+ return trim($node->nodeValue) !== "";
+ }
+
+ return true;
+ }
+
+ /**
+ * "Destructor": forcibly free all references held by this frame
+ *
+ * @param bool $recursive if true, call dispose on all children
+ */
+ public function dispose($recursive = false)
+ {
+ if ($recursive) {
+ while ($child = $this->_first_child) {
+ $child->dispose(true);
+ }
+ }
+
+ // Remove this frame from the tree
+ if ($this->_prev_sibling) {
+ $this->_prev_sibling->_next_sibling = $this->_next_sibling;
+ }
+
+ if ($this->_next_sibling) {
+ $this->_next_sibling->_prev_sibling = $this->_prev_sibling;
+ }
+
+ if ($this->_parent && $this->_parent->_first_child === $this) {
+ $this->_parent->_first_child = $this->_next_sibling;
+ }
+
+ if ($this->_parent && $this->_parent->_last_child === $this) {
+ $this->_parent->_last_child = $this->_prev_sibling;
+ }
+
+ if ($this->_parent) {
+ $this->_parent->get_node()->removeChild($this->_node);
+ }
+
+ $this->_style = null;
+ unset($this->_style);
+ }
+
+ /**
+ * Re-initialize the frame
+ */
+ public function reset()
+ {
+ $this->_position["x"] = null;
+ $this->_position["y"] = null;
+
+ $this->_containing_block["x"] = null;
+ $this->_containing_block["y"] = null;
+ $this->_containing_block["w"] = null;
+ $this->_containing_block["h"] = null;
+
+ $this->_style->reset();
+ }
+
+ /**
+ * @return \DOMElement|\DOMText
+ */
+ public function get_node()
+ {
+ return $this->_node;
+ }
+
+ /**
+ * @return int
+ */
+ public function get_id()
+ {
+ return $this->_id;
+ }
+
+ /**
+ * @return Style
+ */
+ public function get_style()
+ {
+ return $this->_style;
+ }
+
+ /**
+ * @deprecated
+ * @return Style
+ */
+ public function get_original_style()
+ {
+ return $this->_style;
+ }
+
+ /**
+ * @return Frame
+ */
+ public function get_parent()
+ {
+ return $this->_parent;
+ }
+
+ /**
+ * @return FrameDecorator\AbstractFrameDecorator
+ */
+ public function get_decorator()
+ {
+ return $this->_decorator;
+ }
+
+ /**
+ * @return Frame
+ */
+ public function get_first_child()
+ {
+ return $this->_first_child;
+ }
+
+ /**
+ * @return Frame
+ */
+ public function get_last_child()
+ {
+ return $this->_last_child;
+ }
+
+ /**
+ * @return Frame
+ */
+ public function get_prev_sibling()
+ {
+ return $this->_prev_sibling;
+ }
+
+ /**
+ * @return Frame
+ */
+ public function get_next_sibling()
+ {
+ return $this->_next_sibling;
+ }
+
+ /**
+ * @return FrameListIterator
+ */
+ public function get_children(): FrameListIterator
+ {
+ return new FrameListIterator($this);
+ }
+
+ // Layout property accessors
+
+ /**
+ * Containing block dimensions
+ *
+ * @param string|null $i The key of the wanted containing block's dimension (x, y, w, h)
+ *
+ * @return float[]|float
+ */
+ public function get_containing_block($i = null)
+ {
+ if (isset($i)) {
+ return $this->_containing_block[$i];
+ }
+
+ return $this->_containing_block;
+ }
+
+ /**
+ * Block position
+ *
+ * @param string|null $i The key of the wanted position value (x, y)
+ *
+ * @return float[]|float
+ */
+ public function get_position($i = null)
+ {
+ if (isset($i)) {
+ return $this->_position[$i];
+ }
+
+ return $this->_position;
+ }
+
+ //........................................................................
+
+ /**
+ * Return the width of the margin box of the frame, in pt. Meaningless
+ * unless the width has been calculated properly.
+ *
+ * @return float
+ */
+ public function get_margin_width(): float
+ {
+ $style = $this->_style;
+
+ return (float)$style->length_in_pt([
+ $style->width,
+ $style->margin_left,
+ $style->margin_right,
+ $style->border_left_width,
+ $style->border_right_width,
+ $style->padding_left,
+ $style->padding_right
+ ], $this->_containing_block["w"]);
+ }
+
+ /**
+ * Return the height of the margin box of the frame, in pt. Meaningless
+ * unless the height has been calculated properly.
+ *
+ * @return float
+ */
+ public function get_margin_height(): float
+ {
+ $style = $this->_style;
+
+ return (float)$style->length_in_pt(
+ [
+ $style->height,
+ (float)$style->length_in_pt(
+ [
+ $style->border_top_width,
+ $style->border_bottom_width,
+ $style->margin_top,
+ $style->margin_bottom,
+ $style->padding_top,
+ $style->padding_bottom
+ ], $this->_containing_block["w"]
+ )
+ ],
+ $this->_containing_block["h"]
+ );
+ }
+
+ /**
+ * Return the content box (x,y,w,h) of the frame.
+ *
+ * Width and height might be reported as 0 if they have not been resolved
+ * yet.
+ *
+ * @return float[]
+ */
+ public function get_content_box(): array
+ {
+ $style = $this->_style;
+ $cb = $this->_containing_block;
+
+ $x = $this->_position["x"] +
+ (float)$style->length_in_pt(
+ [
+ $style->margin_left,
+ $style->border_left_width,
+ $style->padding_left
+ ],
+ $cb["w"]
+ );
+
+ $y = $this->_position["y"] +
+ (float)$style->length_in_pt(
+ [
+ $style->margin_top,
+ $style->border_top_width,
+ $style->padding_top
+ ], $cb["w"]
+ );
+
+ $w = (float)$style->length_in_pt($style->width, $cb["w"]);
+
+ $h = (float)$style->length_in_pt($style->height, $cb["h"]);
+
+ return [0 => $x, "x" => $x,
+ 1 => $y, "y" => $y,
+ 2 => $w, "w" => $w,
+ 3 => $h, "h" => $h];
+ }
+
+ /**
+ * Return the padding box (x,y,w,h) of the frame.
+ *
+ * Width and height might be reported as 0 if they have not been resolved
+ * yet.
+ *
+ * @return float[]
+ */
+ public function get_padding_box(): array
+ {
+ $style = $this->_style;
+ $cb = $this->_containing_block;
+
+ $x = $this->_position["x"] +
+ (float)$style->length_in_pt(
+ [
+ $style->margin_left,
+ $style->border_left_width
+ ],
+ $cb["w"]
+ );
+
+ $y = $this->_position["y"] +
+ (float)$style->length_in_pt(
+ [
+ $style->margin_top,
+ $style->border_top_width
+ ],
+ $cb["h"]
+ );
+
+ $w = (float)$style->length_in_pt(
+ [
+ $style->padding_left,
+ $style->width,
+ $style->padding_right
+ ],
+ $cb["w"]
+ );
+
+ $h = (float)$style->length_in_pt(
+ [
+ $style->padding_top,
+ $style->padding_bottom,
+ $style->length_in_pt($style->height, $cb["h"])
+ ],
+ $cb["w"]
+ );
+
+ return [0 => $x, "x" => $x,
+ 1 => $y, "y" => $y,
+ 2 => $w, "w" => $w,
+ 3 => $h, "h" => $h];
+ }
+
+ /**
+ * Return the border box of the frame.
+ *
+ * Width and height might be reported as 0 if they have not been resolved
+ * yet.
+ *
+ * @return float[]
+ */
+ public function get_border_box(): array
+ {
+ $style = $this->_style;
+ $cb = $this->_containing_block;
+
+ $x = $this->_position["x"] + (float)$style->length_in_pt($style->margin_left, $cb["w"]);
+
+ $y = $this->_position["y"] + (float)$style->length_in_pt($style->margin_top, $cb["w"]);
+
+ $w = (float)$style->length_in_pt(
+ [
+ $style->border_left_width,
+ $style->padding_left,
+ $style->width,
+ $style->padding_right,
+ $style->border_right_width
+ ],
+ $cb["w"]
+ );
+
+ $h = (float)$style->length_in_pt(
+ [
+ $style->border_top_width,
+ $style->padding_top,
+ $style->padding_bottom,
+ $style->border_bottom_width,
+ $style->length_in_pt($style->height, $cb["h"])
+ ],
+ $cb["w"]
+ );
+
+ return [0 => $x, "x" => $x,
+ 1 => $y, "y" => $y,
+ 2 => $w, "w" => $w,
+ 3 => $h, "h" => $h];
+ }
+
+ /**
+ * @param float|null $opacity
+ *
+ * @return float
+ */
+ public function get_opacity(?float $opacity = null): float
+ {
+ if ($opacity !== null) {
+ $this->set_opacity($opacity);
+ }
+
+ return $this->_opacity;
+ }
+
+ /**
+ * @return LineBox|null
+ */
+ public function &get_containing_line()
+ {
+ return $this->_containing_line;
+ }
+
+ //........................................................................
+ // Set methods
+
+ /**
+ * @param int $id
+ */
+ public function set_id($id)
+ {
+ $this->_id = $id;
+
+ // We can only set attributes of DOMElement objects (nodeType == 1).
+ // Since these are the only objects that we can assign CSS rules to,
+ // this shortcoming is okay.
+ if ($this->_node->nodeType == XML_ELEMENT_NODE) {
+ $this->_node->setAttribute("frame_id", $id);
+ }
+ }
+
+ /**
+ * @param Style $style
+ */
+ public function set_style(Style $style): void
+ {
+ // $style->set_frame($this);
+ $this->_style = $style;
+ }
+
+ /**
+ * @param FrameDecorator\AbstractFrameDecorator $decorator
+ */
+ public function set_decorator(FrameDecorator\AbstractFrameDecorator $decorator)
+ {
+ $this->_decorator = $decorator;
+ }
+
+ /**
+ * @param float|float[]|null $x
+ * @param float|null $y
+ * @param float|null $w
+ * @param float|null $h
+ */
+ public function set_containing_block($x = null, $y = null, $w = null, $h = null)
+ {
+ if (is_array($x)) {
+ foreach ($x as $key => $val) {
+ $$key = $val;
+ }
+ }
+
+ if (is_numeric($x)) {
+ $this->_containing_block["x"] = $x;
+ }
+
+ if (is_numeric($y)) {
+ $this->_containing_block["y"] = $y;
+ }
+
+ if (is_numeric($w)) {
+ $this->_containing_block["w"] = $w;
+ }
+
+ if (is_numeric($h)) {
+ $this->_containing_block["h"] = $h;
+ }
+ }
+
+ /**
+ * @param float|float[]|null $x
+ * @param float|null $y
+ */
+ public function set_position($x = null, $y = null)
+ {
+ if (is_array($x)) {
+ list($x, $y) = [$x["x"], $x["y"]];
+ }
+
+ if (is_numeric($x)) {
+ $this->_position["x"] = $x;
+ }
+
+ if (is_numeric($y)) {
+ $this->_position["y"] = $y;
+ }
+ }
+
+ /**
+ * @param float $opacity
+ */
+ public function set_opacity(float $opacity): void
+ {
+ $parent = $this->get_parent();
+ $base_opacity = $parent && $parent->_opacity !== null ? $parent->_opacity : 1.0;
+ $this->_opacity = $base_opacity * $opacity;
+ }
+
+ /**
+ * @param LineBox $line
+ */
+ public function set_containing_line(LineBox $line)
+ {
+ $this->_containing_line = $line;
+ }
+
+ /**
+ * Indicates if the margin height is auto sized
+ *
+ * @return bool
+ */
+ public function is_auto_height()
+ {
+ $style = $this->_style;
+
+ return in_array(
+ "auto",
+ [
+ $style->height,
+ $style->margin_top,
+ $style->margin_bottom,
+ $style->border_top_width,
+ $style->border_bottom_width,
+ $style->padding_top,
+ $style->padding_bottom,
+ $this->_containing_block["h"]
+ ],
+ true
+ );
+ }
+
+ /**
+ * Indicates if the margin width is auto sized
+ *
+ * @return bool
+ */
+ public function is_auto_width()
+ {
+ $style = $this->_style;
+
+ return in_array(
+ "auto",
+ [
+ $style->width,
+ $style->margin_left,
+ $style->margin_right,
+ $style->border_left_width,
+ $style->border_right_width,
+ $style->padding_left,
+ $style->padding_right,
+ $this->_containing_block["w"]
+ ],
+ true
+ );
+ }
+
+ /**
+ * Tells if the frame is a text node
+ *
+ * @return bool
+ */
+ public function is_text_node(): bool
+ {
+ if (isset($this->_is_cache["text_node"])) {
+ return $this->_is_cache["text_node"];
+ }
+
+ return $this->_is_cache["text_node"] = ($this->get_node()->nodeName === "#text");
+ }
+
+ /**
+ * @return bool
+ */
+ public function is_positioned(): bool
+ {
+ if (isset($this->_is_cache["positioned"])) {
+ return $this->_is_cache["positioned"];
+ }
+
+ $position = $this->get_style()->position;
+
+ return $this->_is_cache["positioned"] = in_array($position, Style::POSITIONED_TYPES, true);
+ }
+
+ /**
+ * @return bool
+ */
+ public function is_absolute(): bool
+ {
+ if (isset($this->_is_cache["absolute"])) {
+ return $this->_is_cache["absolute"];
+ }
+
+ return $this->_is_cache["absolute"] = $this->get_style()->is_absolute();
+ }
+
+ /**
+ * Whether the frame is a block container.
+ *
+ * @return bool
+ */
+ public function is_block(): bool
+ {
+ if (isset($this->_is_cache["block"])) {
+ return $this->_is_cache["block"];
+ }
+
+ return $this->_is_cache["block"] = in_array($this->get_style()->display, Style::BLOCK_TYPES, true);
+ }
+
+ /**
+ * Whether the frame has a block-level display type.
+ *
+ * @return bool
+ */
+ public function is_block_level(): bool
+ {
+ if (isset($this->_is_cache["block_level"])) {
+ return $this->_is_cache["block_level"];
+ }
+
+ $display = $this->get_style()->display;
+
+ return $this->_is_cache["block_level"] = in_array($display, Style::BLOCK_LEVEL_TYPES, true);
+ }
+
+ /**
+ * Whether the frame has an inline-level display type.
+ *
+ * @return bool
+ */
+ public function is_inline_level(): bool
+ {
+ if (isset($this->_is_cache["inline_level"])) {
+ return $this->_is_cache["inline_level"];
+ }
+
+ $display = $this->get_style()->display;
+
+ return $this->_is_cache["inline_level"] = in_array($display, Style::INLINE_LEVEL_TYPES, true);
+ }
+
+ /**
+ * @return bool
+ */
+ public function is_in_flow(): bool
+ {
+ if (isset($this->_is_cache["in_flow"])) {
+ return $this->_is_cache["in_flow"];
+ }
+
+ return $this->_is_cache["in_flow"] = $this->get_style()->is_in_flow();
+ }
+
+ /**
+ * @return bool
+ */
+ public function is_pre(): bool
+ {
+ if (isset($this->_is_cache["pre"])) {
+ return $this->_is_cache["pre"];
+ }
+
+ $white_space = $this->get_style()->white_space;
+
+ return $this->_is_cache["pre"] = in_array($white_space, ["pre", "pre-wrap"], true);
+ }
+
+ /**
+ * @return bool
+ */
+ public function is_table(): bool
+ {
+ if (isset($this->_is_cache["table"])) {
+ return $this->_is_cache["table"];
+ }
+
+ $display = $this->get_style()->display;
+
+ return $this->_is_cache["table"] = in_array($display, Style::TABLE_TYPES, true);
+ }
+
+
+ /**
+ * Inserts a new child at the beginning of the Frame
+ *
+ * @param Frame $child The new Frame to insert
+ * @param bool $update_node Whether or not to update the DOM
+ */
+ public function prepend_child(Frame $child, $update_node = true)
+ {
+ if ($update_node) {
+ $this->_node->insertBefore($child->_node, $this->_first_child ? $this->_first_child->_node : null);
+ }
+
+ // Remove the child from its parent
+ if ($child->_parent) {
+ $child->_parent->remove_child($child, false);
+ }
+
+ $child->_parent = $this;
+ $decorator = $child->get_decorator();
+ // force an update to the cached parent
+ if ($decorator !== null) {
+ $decorator->get_parent(false);
+ }
+ $child->_prev_sibling = null;
+
+ // Handle the first child
+ if (!$this->_first_child) {
+ $this->_first_child = $child;
+ $this->_last_child = $child;
+ $child->_next_sibling = null;
+ } else {
+ $this->_first_child->_prev_sibling = $child;
+ $child->_next_sibling = $this->_first_child;
+ $this->_first_child = $child;
+ }
+ }
+
+ /**
+ * Inserts a new child at the end of the Frame
+ *
+ * @param Frame $child The new Frame to insert
+ * @param bool $update_node Whether or not to update the DOM
+ */
+ public function append_child(Frame $child, $update_node = true)
+ {
+ if ($update_node) {
+ $this->_node->appendChild($child->_node);
+ }
+
+ // Remove the child from its parent
+ if ($child->_parent) {
+ $child->_parent->remove_child($child, false);
+ }
+
+ $child->_parent = $this;
+ $decorator = $child->get_decorator();
+ // force an update to the cached parent
+ if ($decorator !== null) {
+ $decorator->get_parent(false);
+ }
+ $child->_next_sibling = null;
+
+ // Handle the first child
+ if (!$this->_last_child) {
+ $this->_first_child = $child;
+ $this->_last_child = $child;
+ $child->_prev_sibling = null;
+ } else {
+ $this->_last_child->_next_sibling = $child;
+ $child->_prev_sibling = $this->_last_child;
+ $this->_last_child = $child;
+ }
+ }
+
+ /**
+ * Inserts a new child immediately before the specified frame
+ *
+ * @param Frame $new_child The new Frame to insert
+ * @param Frame $ref The Frame after the new Frame
+ * @param bool $update_node Whether or not to update the DOM
+ *
+ * @throws Exception
+ */
+ public function insert_child_before(Frame $new_child, Frame $ref, $update_node = true)
+ {
+ if ($ref === $this->_first_child) {
+ $this->prepend_child($new_child, $update_node);
+
+ return;
+ }
+
+ if (is_null($ref)) {
+ $this->append_child($new_child, $update_node);
+
+ return;
+ }
+
+ if ($ref->_parent !== $this) {
+ throw new Exception("Reference child is not a child of this node.");
+ }
+
+ // Update the node
+ if ($update_node) {
+ $this->_node->insertBefore($new_child->_node, $ref->_node);
+ }
+
+ // Remove the child from its parent
+ if ($new_child->_parent) {
+ $new_child->_parent->remove_child($new_child, false);
+ }
+
+ $new_child->_parent = $this;
+ $decorator = $new_child->get_decorator();
+ // force an update to the cached parent
+ if ($decorator !== null) {
+ $decorator->get_parent(false);
+ }
+ $new_child->_next_sibling = $ref;
+ $new_child->_prev_sibling = $ref->_prev_sibling;
+
+ if ($ref->_prev_sibling) {
+ $ref->_prev_sibling->_next_sibling = $new_child;
+ }
+
+ $ref->_prev_sibling = $new_child;
+ }
+
+ /**
+ * Inserts a new child immediately after the specified frame
+ *
+ * @param Frame $new_child The new Frame to insert
+ * @param Frame $ref The Frame before the new Frame
+ * @param bool $update_node Whether or not to update the DOM
+ *
+ * @throws Exception
+ */
+ public function insert_child_after(Frame $new_child, Frame $ref, $update_node = true)
+ {
+ if ($ref === $this->_last_child) {
+ $this->append_child($new_child, $update_node);
+
+ return;
+ }
+
+ if (is_null($ref)) {
+ $this->prepend_child($new_child, $update_node);
+
+ return;
+ }
+
+ if ($ref->_parent !== $this) {
+ throw new Exception("Reference child is not a child of this node.");
+ }
+
+ // Update the node
+ if ($update_node) {
+ if ($ref->_next_sibling) {
+ $next_node = $ref->_next_sibling->_node;
+ $this->_node->insertBefore($new_child->_node, $next_node);
+ } else {
+ $new_child->_node = $this->_node->appendChild($new_child->_node);
+ }
+ }
+
+ // Remove the child from its parent
+ if ($new_child->_parent) {
+ $new_child->_parent->remove_child($new_child, false);
+ }
+
+ $new_child->_parent = $this;
+ $decorator = $new_child->get_decorator();
+ // force an update to the cached parent
+ if ($decorator !== null) {
+ $decorator->get_parent(false);
+ }
+ $new_child->_prev_sibling = $ref;
+ $new_child->_next_sibling = $ref->_next_sibling;
+
+ if ($ref->_next_sibling) {
+ $ref->_next_sibling->_prev_sibling = $new_child;
+ }
+
+ $ref->_next_sibling = $new_child;
+ }
+
+ /**
+ * Remove a child frame
+ *
+ * @param Frame $child
+ * @param bool $update_node Whether or not to remove the DOM node
+ *
+ * @throws Exception
+ * @return Frame The removed child frame
+ */
+ public function remove_child(Frame $child, $update_node = true)
+ {
+ if ($child->_parent !== $this) {
+ throw new Exception("Child not found in this frame");
+ }
+
+ if ($update_node) {
+ $this->_node->removeChild($child->_node);
+ }
+
+ if ($child === $this->_first_child) {
+ $this->_first_child = $child->_next_sibling;
+ }
+
+ if ($child === $this->_last_child) {
+ $this->_last_child = $child->_prev_sibling;
+ }
+
+ if ($child->_prev_sibling) {
+ $child->_prev_sibling->_next_sibling = $child->_next_sibling;
+ }
+
+ if ($child->_next_sibling) {
+ $child->_next_sibling->_prev_sibling = $child->_prev_sibling;
+ }
+
+ $child->_next_sibling = null;
+ $child->_prev_sibling = null;
+ $child->_parent = null;
+
+ // Force an update to the cached decorator parent
+ $decorator = $child->get_decorator();
+ if ($decorator !== null) {
+ $decorator->get_parent(false);
+ }
+
+ return $child;
+ }
+
+ //........................................................................
+
+ // Debugging function:
+ /**
+ * @return string
+ */
+ public function __toString()
+ {
+ // Skip empty text frames
+// if ( $this->is_text_node() &&
+// preg_replace("/\s/", "", $this->_node->data) === "" )
+// return "";
+
+
+ $str = "" . $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, "UTF-8") .
+ (mb_strlen($tmp, "UTF-8") > 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: " . Helpers::pre_r($this->_position, true);
+ $str .= "\nContaining block: " . Helpers::pre_r($this->_containing_block, true);
+ $str .= "\nMargin width: " . Helpers::pre_r($this->get_margin_width(), true);
+ $str .= "\nMargin height: " . Helpers::pre_r($this->get_margin_height(), true);
+
+ $str .= "\nStyle: " . $this->_style->__toString() . "
";
+
+ if ($this->_decorator instanceof FrameDecorator\Block) {
+ $str .= "Lines:";
+ foreach ($this->_decorator->get_line_boxes() as $line) {
+ foreach ($line->get_frames() as $frame) {
+ if ($frame instanceof FrameDecorator\Text) {
+ $str .= "\ntext: ";
+ $str .= "'" . htmlspecialchars($frame->get_text()) . "'";
+ } else {
+ $str .= "\nBlock: " . $frame->get_node()->nodeName . " (" . spl_object_hash($frame->get_node()) . ")";
+ }
+ }
+
+ $str .=
+ "\ny => " . $line->y . "\n" .
+ "w => " . $line->w . "\n" .
+ "h => " . $line->h . "\n" .
+ "left => " . $line->left . "\n" .
+ "right => " . $line->right . "\n";
+ }
+ $str .= "";
+ }
+
+ $str .= "\n";
+ if (php_sapi_name() === "cli") {
+ $str = strip_tags(str_replace(["
", "", ""],
+ ["\n", "", ""],
+ $str));
+ }
+
+ return $str;
+ }
+}
diff --git a/app/libs/dompdf/vendor/dompdf/dompdf/src/Frame/Factory.php b/app/libs/dompdf/vendor/dompdf/dompdf/src/Frame/Factory.php
new file mode 100644
index 0000000..be39ff3
--- /dev/null
+++ b/app/libs/dompdf/vendor/dompdf/dompdf/src/Frame/Factory.php
@@ -0,0 +1,262 @@
+set_reflower(new PageFrameReflower($frame));
+ $root->set_decorator($frame);
+
+ return $frame;
+ }
+
+ /**
+ * Decorate a Frame
+ *
+ * @param Frame $frame The frame to decorate
+ * @param Dompdf $dompdf The dompdf instance
+ * @param Frame|null $root The root of the frame
+ *
+ * @throws Exception
+ * @return AbstractFrameDecorator|null
+ * FIXME: this is admittedly a little smelly...
+ */
+ public static function decorate_frame(Frame $frame, Dompdf $dompdf, ?Frame $root = null): ?AbstractFrameDecorator
+ {
+ $style = $frame->get_style();
+ $display = $style->display;
+
+ switch ($display) {
+
+ case "block":
+ $positioner = "Block";
+ $decorator = "Block";
+ $reflower = "Block";
+ break;
+
+ case "inline-block":
+ $positioner = "Inline";
+ $decorator = "Block";
+ $reflower = "Block";
+ break;
+
+ case "inline":
+ $positioner = "Inline";
+ if ($frame->is_text_node()) {
+ $decorator = "Text";
+ $reflower = "Text";
+ } else {
+ $decorator = "Inline";
+ $reflower = "Inline";
+ }
+ break;
+
+ case "table":
+ $positioner = "Block";
+ $decorator = "Table";
+ $reflower = "Table";
+ break;
+
+ case "inline-table":
+ $positioner = "Inline";
+ $decorator = "Table";
+ $reflower = "Table";
+ break;
+
+ case "table-row-group":
+ case "table-header-group":
+ case "table-footer-group":
+ $positioner = "NullPositioner";
+ $decorator = "TableRowGroup";
+ $reflower = "TableRowGroup";
+ break;
+
+ case "table-row":
+ $positioner = "NullPositioner";
+ $decorator = "TableRow";
+ $reflower = "TableRow";
+ break;
+
+ case "table-cell":
+ $positioner = "TableCell";
+ $decorator = "TableCell";
+ $reflower = "TableCell";
+ break;
+
+ case "list-item":
+ $positioner = "Block";
+ $decorator = "Block";
+ $reflower = "Block";
+ break;
+
+ case "-dompdf-list-bullet":
+ if ($style->list_style_position === "inside") {
+ $positioner = "Inline";
+ } else {
+ $positioner = "ListBullet";
+ }
+
+ if ($style->list_style_image !== "none") {
+ $decorator = "ListBulletImage";
+ } else {
+ $decorator = "ListBullet";
+ }
+
+ $reflower = "ListBullet";
+ break;
+
+ case "-dompdf-image":
+ $positioner = "Inline";
+ $decorator = "Image";
+ $reflower = "Image";
+ break;
+
+ case "-dompdf-br":
+ $positioner = "Inline";
+ $decorator = "Inline";
+ $reflower = "Inline";
+ break;
+
+ default:
+ case "none":
+ if ($style->_dompdf_keep !== "yes") {
+ // Remove the node and the frame
+ $frame->get_parent()->remove_child($frame);
+ return null;
+ }
+
+ $positioner = "NullPositioner";
+ $decorator = "NullFrameDecorator";
+ $reflower = "NullFrameReflower";
+ break;
+ }
+
+ // Handle CSS position
+ $position = $style->position;
+
+ if ($position === "absolute") {
+ $positioner = "Absolute";
+ } elseif ($position === "fixed") {
+ $positioner = "Fixed";
+ }
+
+ $node = $frame->get_node();
+
+ // Handle nodeName
+ if ($node->nodeName === "img") {
+ $style->set_prop("display", "-dompdf-image");
+ $decorator = "Image";
+ $reflower = "Image";
+ }
+
+ $decorator = "Dompdf\\FrameDecorator\\$decorator";
+ $reflower = "Dompdf\\FrameReflower\\$reflower";
+
+ /** @var AbstractFrameDecorator $deco */
+ $deco = new $decorator($frame, $dompdf);
+
+ $deco->set_positioner(self::getPositionerInstance($positioner));
+ $deco->set_reflower(new $reflower($deco, $dompdf->getFontMetrics()));
+
+ if ($root) {
+ $deco->set_root($root);
+ }
+
+ if ($display === "list-item") {
+ // Insert a list-bullet frame
+ $xml = $dompdf->getDom();
+ $bullet_node = $xml->createElement("bullet"); // arbitrary choice
+ $b_f = new Frame($bullet_node);
+
+ $node = $frame->get_node();
+ $parent_node = $node->parentNode;
+ if ($parent_node && $parent_node instanceof \DOMElement) {
+ if (!$parent_node->hasAttribute("dompdf-children-count")) {
+ $xpath = new DOMXPath($xml);
+ $count = $xpath->query("li", $parent_node)->length;
+ $parent_node->setAttribute("dompdf-children-count", $count);
+ }
+
+ if (is_numeric($node->getAttribute("value"))) {
+ $index = intval($node->getAttribute("value"));
+ } else {
+ if (!$parent_node->hasAttribute("dompdf-counter")) {
+ $index = ($parent_node->hasAttribute("start") ? $parent_node->getAttribute("start") : 1);
+ } else {
+ $index = (int)$parent_node->getAttribute("dompdf-counter") + 1;
+ }
+ }
+
+ $parent_node->setAttribute("dompdf-counter", $index);
+ $bullet_node->setAttribute("dompdf-counter", $index);
+ }
+
+ $new_style = $dompdf->getCss()->create_style();
+ $new_style->set_prop("display", "-dompdf-list-bullet");
+ $new_style->inherit($style);
+ $b_f->set_style($new_style);
+
+ $deco->prepend_child(Factory::decorate_frame($b_f, $dompdf, $root));
+ }
+
+ return $deco;
+ }
+
+ /**
+ * Creates Positioners
+ *
+ * @param string $type Type of positioner to use
+ *
+ * @return AbstractPositioner
+ */
+ protected static function getPositionerInstance(string $type): AbstractPositioner
+ {
+ if (!isset(self::$_positioners[$type])) {
+ $class = '\\Dompdf\\Positioner\\'.$type;
+ self::$_positioners[$type] = new $class();
+ }
+ return self::$_positioners[$type];
+ }
+}
diff --git a/app/libs/dompdf/vendor/dompdf/dompdf/src/Frame/FrameListIterator.php b/app/libs/dompdf/vendor/dompdf/dompdf/src/Frame/FrameListIterator.php
new file mode 100644
index 0000000..0157550
--- /dev/null
+++ b/app/libs/dompdf/vendor/dompdf/dompdf/src/Frame/FrameListIterator.php
@@ -0,0 +1,100 @@
+parent = $frame;
+ $this->rewind();
+ }
+
+ public function rewind(): void
+ {
+ $this->cur = $this->parent->get_first_child();
+ $this->prev = null;
+ $this->num = 0;
+ }
+
+ /**
+ * @return bool
+ */
+ public function valid(): bool
+ {
+ return $this->cur !== null;
+ }
+
+ /**
+ * @return int
+ */
+ public function key(): int
+ {
+ return $this->num;
+ }
+
+ /**
+ * @return Frame|null
+ */
+ public function current(): ?Frame
+ {
+ return $this->cur;
+ }
+
+ public function next(): void
+ {
+ if ($this->cur === null) {
+ return;
+ }
+
+ if ($this->cur->get_parent() === $this->parent) {
+ $this->prev = $this->cur;
+ $this->cur = $this->cur->get_next_sibling();
+ $this->num++;
+ } else {
+ // Continue from the previous child if the current frame has been
+ // moved to another parent
+ $this->cur = $this->prev !== null
+ ? $this->prev->get_next_sibling()
+ : $this->parent->get_first_child();
+ }
+ }
+}
diff --git a/app/libs/dompdf/vendor/dompdf/dompdf/src/Frame/FrameTree.php b/app/libs/dompdf/vendor/dompdf/dompdf/src/Frame/FrameTree.php
new file mode 100644
index 0000000..6d012d8
--- /dev/null
+++ b/app/libs/dompdf/vendor/dompdf/dompdf/src/Frame/FrameTree.php
@@ -0,0 +1,324 @@
+_dom = $dom;
+ $this->_root = null;
+ $this->_registry = [];
+ }
+
+ /**
+ * Returns the DOMDocument object representing the current html document
+ *
+ * @return DOMDocument
+ */
+ public function get_dom()
+ {
+ return $this->_dom;
+ }
+
+ /**
+ * Returns the root frame of the tree
+ *
+ * @return Frame
+ */
+ public function get_root()
+ {
+ return $this->_root;
+ }
+
+ /**
+ * Returns a specific frame given its id
+ *
+ * @param string $id
+ *
+ * @return Frame|null
+ */
+ public function get_frame($id)
+ {
+ return isset($this->_registry[$id]) ? $this->_registry[$id] : null;
+ }
+
+ /**
+ * Returns a post-order iterator for all frames in the tree
+ *
+ * @deprecated Iterate the tree directly instead
+ * @return FrameTreeIterator
+ */
+ public function get_frames(): FrameTreeIterator
+ {
+ return new FrameTreeIterator($this->_root);
+ }
+
+ /**
+ * Returns a post-order iterator for all frames in the tree
+ *
+ * @return FrameTreeIterator
+ */
+ public function getIterator(): FrameTreeIterator
+ {
+ return new FrameTreeIterator($this->_root);
+ }
+
+ /**
+ * Builds the tree
+ */
+ public function build_tree()
+ {
+ $html = $this->_dom->getElementsByTagName("html")->item(0);
+ if (is_null($html)) {
+ $html = $this->_dom->firstChild;
+ }
+
+ if (is_null($html)) {
+ throw new Exception("Requested HTML document contains no data.");
+ }
+
+ $this->fix_tables();
+
+ $this->_root = $this->_build_tree_r($html);
+ }
+
+ /**
+ * Adds missing TBODYs around TR
+ */
+ protected function fix_tables()
+ {
+ $xp = new DOMXPath($this->_dom);
+
+ // Move table caption before the table
+ // FIXME find a better way to deal with it...
+ $captions = $xp->query('//table/caption');
+ foreach ($captions as $caption) {
+ $table = $caption->parentNode;
+ $table->parentNode->insertBefore($caption, $table);
+ }
+
+ $firstRows = $xp->query('//table/tr[1]');
+ /** @var DOMElement $tableChild */
+ foreach ($firstRows as $tableChild) {
+ $tbody = $this->_dom->createElement('tbody');
+ $tableNode = $tableChild->parentNode;
+ do {
+ if ($tableChild->nodeName === 'tr') {
+ $tmpNode = $tableChild;
+ $tableChild = $tableChild->nextSibling;
+ $tableNode->removeChild($tmpNode);
+ $tbody->appendChild($tmpNode);
+ } else {
+ if ($tbody->hasChildNodes() === true) {
+ $tableNode->insertBefore($tbody, $tableChild);
+ $tbody = $this->_dom->createElement('tbody');
+ }
+ $tableChild = $tableChild->nextSibling;
+ }
+ } while ($tableChild);
+ if ($tbody->hasChildNodes() === true) {
+ $tableNode->appendChild($tbody);
+ }
+ }
+ }
+
+ // FIXME: temporary hack, preferably we will improve rendering of sequential #text nodes
+ /**
+ * Remove a child from a node
+ *
+ * Remove a child from a node. If the removed node results in two
+ * adjacent #text nodes then combine them.
+ *
+ * @param DOMNode $node the current DOMNode being considered
+ * @param array $children an array of nodes that are the children of $node
+ * @param int $index index from the $children array of the node to remove
+ */
+ protected function _remove_node(DOMNode $node, array &$children, $index)
+ {
+ $child = $children[$index];
+ $previousChild = $child->previousSibling;
+ $nextChild = $child->nextSibling;
+ $node->removeChild($child);
+ if (isset($previousChild, $nextChild)) {
+ if ($previousChild->nodeName === "#text" && $nextChild->nodeName === "#text") {
+ $previousChild->nodeValue .= $nextChild->nodeValue;
+ $this->_remove_node($node, $children, $index+1);
+ }
+ }
+ array_splice($children, $index, 1);
+ }
+
+ /**
+ * Recursively adds {@link Frame} objects to the tree
+ *
+ * Recursively build a tree of Frame objects based on a dom tree.
+ * No layout information is calculated at this time, although the
+ * tree may be adjusted (i.e. nodes and frames for generated content
+ * and images may be created).
+ *
+ * @param DOMNode $node the current DOMNode being considered
+ *
+ * @return Frame
+ */
+ protected function _build_tree_r(DOMNode $node)
+ {
+ $frame = new Frame($node);
+ $id = $frame->get_id();
+ $this->_registry[$id] = $frame;
+
+ if (!$node->hasChildNodes()) {
+ return $frame;
+ }
+
+ // Store the children in an array so that the tree can be modified
+ $children = [];
+ $length = $node->childNodes->length;
+ for ($i = 0; $i < $length; $i++) {
+ $children[] = $node->childNodes->item($i);
+ }
+ $index = 0;
+ // INFO: We don't advance $index if a node is removed to avoid skipping nodes
+ while ($index < count($children)) {
+ $child = $children[$index];
+ $nodeName = strtolower($child->nodeName);
+
+ // Skip non-displaying nodes
+ if (in_array($nodeName, self::$HIDDEN_TAGS)) {
+ if ($nodeName !== "head" && $nodeName !== "style") {
+ $this->_remove_node($node, $children, $index);
+ } else {
+ $index++;
+ }
+ continue;
+ }
+ // Skip empty text nodes
+ if ($nodeName === "#text" && $child->nodeValue === "") {
+ $this->_remove_node($node, $children, $index);
+ continue;
+ }
+ // Skip empty image nodes
+ if ($nodeName === "img" && $child->getAttribute("src") === "") {
+ $this->_remove_node($node, $children, $index);
+ continue;
+ }
+
+ if (is_object($child)) {
+ $frame->append_child($this->_build_tree_r($child), false);
+ }
+ $index++;
+ }
+
+ return $frame;
+ }
+
+ /**
+ * @param DOMElement $node
+ * @param DOMElement $new_node
+ * @param string $pos
+ *
+ * @return mixed
+ */
+ public function insert_node(DOMElement $node, DOMElement $new_node, $pos)
+ {
+ if ($pos === "after" || !$node->firstChild) {
+ $node->appendChild($new_node);
+ } else {
+ $node->insertBefore($new_node, $node->firstChild);
+ }
+
+ $this->_build_tree_r($new_node);
+
+ $frame_id = $new_node->getAttribute("frame_id");
+ $frame = $this->get_frame($frame_id);
+
+ $parent_id = $node->getAttribute("frame_id");
+ $parent = $this->get_frame($parent_id);
+
+ if ($parent) {
+ if ($pos === "before") {
+ $parent->prepend_child($frame, false);
+ } else {
+ $parent->append_child($frame, false);
+ }
+ }
+
+ return $frame_id;
+ }
+}
diff --git a/app/libs/dompdf/vendor/dompdf/dompdf/src/Frame/FrameTreeIterator.php b/app/libs/dompdf/vendor/dompdf/dompdf/src/Frame/FrameTreeIterator.php
new file mode 100644
index 0000000..4da8da1
--- /dev/null
+++ b/app/libs/dompdf/vendor/dompdf/dompdf/src/Frame/FrameTreeIterator.php
@@ -0,0 +1,88 @@
+_stack[] = $this->_root = $root;
+ $this->_num = 0;
+ }
+
+ public function rewind(): void
+ {
+ $this->_stack = [$this->_root];
+ $this->_num = 0;
+ }
+
+ /**
+ * @return bool
+ */
+ public function valid(): bool
+ {
+ return count($this->_stack) > 0;
+ }
+
+ /**
+ * @return int
+ */
+ public function key(): int
+ {
+ return $this->_num;
+ }
+
+ /**
+ * @return Frame
+ */
+ public function current(): Frame
+ {
+ return end($this->_stack);
+ }
+
+ public function next(): void
+ {
+ $b = array_pop($this->_stack);
+ $this->_num++;
+
+ // Push all children onto the stack in reverse order
+ if ($c = $b->get_last_child()) {
+ $this->_stack[] = $c;
+ while ($c = $c->get_prev_sibling()) {
+ $this->_stack[] = $c;
+ }
+ }
+ }
+}
diff --git a/app/libs/dompdf/vendor/dompdf/dompdf/src/FrameDecorator/AbstractFrameDecorator.php b/app/libs/dompdf/vendor/dompdf/dompdf/src/FrameDecorator/AbstractFrameDecorator.php
new file mode 100644
index 0000000..4ab7e7f
--- /dev/null
+++ b/app/libs/dompdf/vendor/dompdf/dompdf/src/FrameDecorator/AbstractFrameDecorator.php
@@ -0,0 +1,925 @@
+ counter_value) (for generated content)
+ *
+ * @var array
+ */
+ public $_counters = [];
+
+ /**
+ * The root node of the DOM tree
+ *
+ * @var Frame
+ */
+ protected $_root;
+
+ /**
+ * The decorated frame
+ *
+ * @var Frame
+ */
+ protected $_frame;
+
+ /**
+ * AbstractPositioner object used to position this frame (Strategy pattern)
+ *
+ * @var AbstractPositioner
+ */
+ protected $_positioner;
+
+ /**
+ * Reflower object used to calculate frame dimensions (Strategy pattern)
+ *
+ * @var AbstractFrameReflower
+ */
+ protected $_reflower;
+
+ /**
+ * Reference to the current dompdf instance
+ *
+ * @var Dompdf
+ */
+ protected $_dompdf;
+
+ /**
+ * First block parent
+ *
+ * @var Block
+ */
+ private $_block_parent;
+
+ /**
+ * First positioned parent (position: relative | absolute | fixed)
+ *
+ * @var AbstractFrameDecorator
+ */
+ private $_positioned_parent;
+
+ /**
+ * Cache for the get_parent while loop results
+ *
+ * @var Frame
+ */
+ private $_cached_parent;
+
+ /**
+ * Whether generated content and counters have been set.
+ *
+ * @var bool
+ */
+ public $content_set = false;
+
+ /**
+ * Whether the frame has been split
+ *
+ * @var bool
+ */
+ public $is_split = false;
+
+ /**
+ * Whether the frame is a split-off frame
+ *
+ * @var bool
+ */
+ public $is_split_off = false;
+
+ /**
+ * Class constructor
+ *
+ * @param Frame $frame The decoration target
+ * @param Dompdf $dompdf The Dompdf object
+ */
+ function __construct(Frame $frame, Dompdf $dompdf)
+ {
+ $this->_frame = $frame;
+ $this->_root = null;
+ $this->_dompdf = $dompdf;
+ $frame->set_decorator($this);
+ }
+
+ /**
+ * "Destructor": forcibly free all references held by this object
+ *
+ * @param bool $recursive if true, call dispose on all children
+ */
+ function dispose($recursive = false)
+ {
+ if ($recursive) {
+ while ($child = $this->get_first_child()) {
+ $child->dispose(true);
+ }
+ }
+
+ $this->_root = null;
+ unset($this->_root);
+
+ $this->_frame->dispose(true);
+ $this->_frame = null;
+ unset($this->_frame);
+
+ $this->_positioner = null;
+ unset($this->_positioner);
+
+ $this->_reflower = null;
+ unset($this->_reflower);
+ }
+
+ /**
+ * Return a copy of this frame with $node as its node
+ *
+ * @param DOMNode $node
+ *
+ * @return AbstractFrameDecorator
+ */
+ function copy(DOMNode $node)
+ {
+ $frame = new Frame($node);
+ $style = clone $this->_frame->get_style();
+
+ $style->reset();
+ $frame->set_style($style);
+
+ if ($node instanceof DOMElement && $node->hasAttribute("id")) {
+ $node->setAttribute("data-dompdf-original-id", $node->getAttribute("id"));
+ $node->removeAttribute("id");
+ }
+
+ $deco = Factory::decorate_frame($frame, $this->_dompdf, $this->_root);
+
+ if ($this instanceof Text) {
+ $deco->trailingWs = $this->trailingWs;
+ }
+
+ return $deco;
+ }
+
+ /**
+ * Create a deep copy: copy this node and all children
+ *
+ * @return AbstractFrameDecorator
+ */
+ function deep_copy()
+ {
+ $node = $this->_frame->get_node()->cloneNode();
+ $frame = new Frame($node);
+ $style = clone $this->_frame->get_style();
+
+ $style->reset();
+ $frame->set_style($style);
+
+ if ($node instanceof DOMElement && $node->hasAttribute("id")) {
+ $node->setAttribute("data-dompdf-original-id", $node->getAttribute("id"));
+ $node->removeAttribute("id");
+ }
+
+ $deco = Factory::decorate_frame($frame, $this->_dompdf, $this->_root);
+
+ if ($this instanceof Text) {
+ $deco->trailingWs = $this->trailingWs;
+ }
+
+ foreach ($this->get_children() as $child) {
+ $deco->append_child($child->deep_copy());
+ }
+
+ return $deco;
+ }
+
+ /**
+ * Create an anonymous child frame, inheriting styles from this frame.
+ *
+ * @param string $node_name
+ * @param string $display
+ *
+ * @return AbstractFrameDecorator
+ */
+ public function create_anonymous_child(string $node_name, string $display): AbstractFrameDecorator
+ {
+ $style = $this->get_style();
+ $child_style = $style->get_stylesheet()->create_style();
+ $child_style->set_prop("display", $display);
+ $child_style->inherit($style);
+
+ $node = $this->get_node()->ownerDocument->createElement($node_name);
+ $frame = new Frame($node);
+ $frame->set_style($child_style);
+
+ return Factory::decorate_frame($frame, $this->_dompdf, $this->_root);
+ }
+
+ function reset()
+ {
+ $this->_frame->reset();
+ $this->_reflower->reset();
+ $this->reset_generated_content();
+ $this->revert_counter_increment();
+
+ $this->content_set = false;
+ $this->_counters = [];
+
+ // clear parent lookup caches
+ $this->_cached_parent = null;
+ $this->_block_parent = null;
+ $this->_positioned_parent = null;
+
+ // Reset all children
+ foreach ($this->get_children() as $child) {
+ $child->reset();
+ }
+ }
+
+ /**
+ * If this represents a generated node then child nodes represent generated
+ * content. Remove the children since the content will be generated next
+ * time this frame is reflowed.
+ */
+ protected function reset_generated_content(): void
+ {
+ if ($this->content_set
+ && $this->get_node()->nodeName === "dompdf_generated"
+ ) {
+ $content = $this->get_style()->content;
+
+ if ($content !== "normal" && $content !== "none") {
+ foreach ($this->get_children() as $child) {
+ $this->remove_child($child);
+ }
+ }
+ }
+ }
+
+ /**
+ * Decrement any counters that were incremented on the current node, unless
+ * that node is the body.
+ */
+ protected function revert_counter_increment(): void
+ {
+ if ($this->content_set
+ && $this->get_node()->nodeName !== "body"
+ && ($decrement = $this->get_style()->counter_increment) !== "none"
+ ) {
+ $this->decrement_counters($decrement);
+ }
+ }
+
+ // Getters -----------
+
+ function get_id()
+ {
+ return $this->_frame->get_id();
+ }
+
+ /**
+ * @return Frame
+ */
+ function get_frame()
+ {
+ return $this->_frame;
+ }
+
+ function get_node()
+ {
+ return $this->_frame->get_node();
+ }
+
+ function get_style()
+ {
+ return $this->_frame->get_style();
+ }
+
+ /**
+ * @deprecated
+ */
+ function get_original_style()
+ {
+ return $this->_frame->get_style();
+ }
+
+ function get_containing_block($i = null)
+ {
+ return $this->_frame->get_containing_block($i);
+ }
+
+ function get_position($i = null)
+ {
+ return $this->_frame->get_position($i);
+ }
+
+ /**
+ * @return Dompdf
+ */
+ function get_dompdf()
+ {
+ return $this->_dompdf;
+ }
+
+ public function get_margin_width(): float
+ {
+ return $this->_frame->get_margin_width();
+ }
+
+ public function get_margin_height(): float
+ {
+ return $this->_frame->get_margin_height();
+ }
+
+ public function get_content_box(): array
+ {
+ return $this->_frame->get_content_box();
+ }
+
+ public function get_padding_box(): array
+ {
+ return $this->_frame->get_padding_box();
+ }
+
+ public function get_border_box(): array
+ {
+ return $this->_frame->get_border_box();
+ }
+
+ function set_id($id)
+ {
+ $this->_frame->set_id($id);
+ }
+
+ public function set_style(Style $style): void
+ {
+ $this->_frame->set_style($style);
+ }
+
+ function set_containing_block($x = null, $y = null, $w = null, $h = null)
+ {
+ $this->_frame->set_containing_block($x, $y, $w, $h);
+ }
+
+ function set_position($x = null, $y = null)
+ {
+ $this->_frame->set_position($x, $y);
+ }
+
+ function is_auto_height()
+ {
+ return $this->_frame->is_auto_height();
+ }
+
+ function is_auto_width()
+ {
+ return $this->_frame->is_auto_width();
+ }
+
+ function __toString()
+ {
+ return $this->_frame->__toString();
+ }
+
+ function prepend_child(Frame $child, $update_node = true)
+ {
+ while ($child instanceof AbstractFrameDecorator) {
+ $child = $child->_frame;
+ }
+
+ $this->_frame->prepend_child($child, $update_node);
+ }
+
+ function append_child(Frame $child, $update_node = true)
+ {
+ while ($child instanceof AbstractFrameDecorator) {
+ $child = $child->_frame;
+ }
+
+ $this->_frame->append_child($child, $update_node);
+ }
+
+ function insert_child_before(Frame $new_child, Frame $ref, $update_node = true)
+ {
+ while ($new_child instanceof AbstractFrameDecorator) {
+ $new_child = $new_child->_frame;
+ }
+
+ if ($ref instanceof AbstractFrameDecorator) {
+ $ref = $ref->_frame;
+ }
+
+ $this->_frame->insert_child_before($new_child, $ref, $update_node);
+ }
+
+ function insert_child_after(Frame $new_child, Frame $ref, $update_node = true)
+ {
+ $insert_frame = $new_child;
+ while ($insert_frame instanceof AbstractFrameDecorator) {
+ $insert_frame = $insert_frame->_frame;
+ }
+
+ $reference_frame = $ref;
+ while ($reference_frame instanceof AbstractFrameDecorator) {
+ $reference_frame = $reference_frame->_frame;
+ }
+
+ $this->_frame->insert_child_after($insert_frame, $reference_frame, $update_node);
+ }
+
+ function remove_child(Frame $child, $update_node = true)
+ {
+ while ($child instanceof AbstractFrameDecorator) {
+ $child = $child->_frame;
+ }
+
+ return $this->_frame->remove_child($child, $update_node);
+ }
+
+ /**
+ * @param bool $use_cache
+ * @return AbstractFrameDecorator
+ */
+ function get_parent($use_cache = true)
+ {
+ if ($use_cache && $this->_cached_parent) {
+ return $this->_cached_parent;
+ }
+ $p = $this->_frame->get_parent();
+ if ($p && $deco = $p->get_decorator()) {
+ while ($tmp = $deco->get_decorator()) {
+ $deco = $tmp;
+ }
+
+ return $this->_cached_parent = $deco;
+ } else {
+ return $this->_cached_parent = $p;
+ }
+ }
+
+ /**
+ * @return AbstractFrameDecorator
+ */
+ function get_first_child()
+ {
+ $c = $this->_frame->get_first_child();
+ if ($c && $deco = $c->get_decorator()) {
+ while ($tmp = $deco->get_decorator()) {
+ $deco = $tmp;
+ }
+
+ return $deco;
+ } else {
+ if ($c) {
+ return $c;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * @return AbstractFrameDecorator
+ */
+ function get_last_child()
+ {
+ $c = $this->_frame->get_last_child();
+ if ($c && $deco = $c->get_decorator()) {
+ while ($tmp = $deco->get_decorator()) {
+ $deco = $tmp;
+ }
+
+ return $deco;
+ } else {
+ if ($c) {
+ return $c;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * @return AbstractFrameDecorator
+ */
+ function get_prev_sibling()
+ {
+ $s = $this->_frame->get_prev_sibling();
+ if ($s && $deco = $s->get_decorator()) {
+ while ($tmp = $deco->get_decorator()) {
+ $deco = $tmp;
+ }
+
+ return $deco;
+ } else {
+ if ($s) {
+ return $s;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * @return AbstractFrameDecorator
+ */
+ function get_next_sibling()
+ {
+ $s = $this->_frame->get_next_sibling();
+ if ($s && $deco = $s->get_decorator()) {
+ while ($tmp = $deco->get_decorator()) {
+ $deco = $tmp;
+ }
+
+ return $deco;
+ } else {
+ if ($s) {
+ return $s;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * @return FrameListIterator
+ */
+ public function get_children(): FrameListIterator
+ {
+ return new FrameListIterator($this);
+ }
+
+ /**
+ * @return FrameTreeIterator
+ */
+ function get_subtree(): FrameTreeIterator
+ {
+ return new FrameTreeIterator($this);
+ }
+
+ function set_positioner(AbstractPositioner $posn)
+ {
+ $this->_positioner = $posn;
+ if ($this->_frame instanceof AbstractFrameDecorator) {
+ $this->_frame->set_positioner($posn);
+ }
+ }
+
+ function set_reflower(AbstractFrameReflower $reflower)
+ {
+ $this->_reflower = $reflower;
+ if ($this->_frame instanceof AbstractFrameDecorator) {
+ $this->_frame->set_reflower($reflower);
+ }
+ }
+
+ /**
+ * @return AbstractPositioner
+ */
+ function get_positioner()
+ {
+ return $this->_positioner;
+ }
+
+ /**
+ * @return AbstractFrameReflower
+ */
+ function get_reflower()
+ {
+ return $this->_reflower;
+ }
+
+ /**
+ * @param Frame $root
+ */
+ function set_root(Frame $root)
+ {
+ $this->_root = $root;
+
+ if ($this->_frame instanceof AbstractFrameDecorator) {
+ $this->_frame->set_root($root);
+ }
+ }
+
+ /**
+ * @return Page
+ */
+ function get_root()
+ {
+ return $this->_root;
+ }
+
+ /**
+ * @return Block
+ */
+ function find_block_parent()
+ {
+ // Find our nearest block level parent
+ if (isset($this->_block_parent)) {
+ return $this->_block_parent;
+ }
+
+ $p = $this->get_parent();
+
+ while ($p) {
+ if ($p->is_block()) {
+ break;
+ }
+
+ $p = $p->get_parent();
+ }
+
+ return $this->_block_parent = $p;
+ }
+
+ /**
+ * @return AbstractFrameDecorator
+ */
+ function find_positioned_parent()
+ {
+ // Find our nearest relative positioned parent
+ if (isset($this->_positioned_parent)) {
+ return $this->_positioned_parent;
+ }
+
+ $p = $this->get_parent();
+ while ($p) {
+ if ($p->is_positioned()) {
+ break;
+ }
+
+ $p = $p->get_parent();
+ }
+
+ if (!$p) {
+ $p = $this->_root;
+ }
+
+ return $this->_positioned_parent = $p;
+ }
+
+ /**
+ * Split this frame at $child.
+ * The current frame is cloned and $child and all children following
+ * $child are added to the clone. The clone is then passed to the
+ * current frame's parent->split() method.
+ *
+ * @param Frame|null $child
+ * @param bool $page_break
+ * @param bool $forced Whether the page break is forced.
+ *
+ * @throws Exception
+ */
+ public function split(?Frame $child = null, bool $page_break = false, bool $forced = false): void
+ {
+ if (is_null($child)) {
+ $this->get_parent()->split($this, $page_break, $forced);
+ return;
+ }
+
+ if ($child->get_parent() !== $this) {
+ throw new Exception("Unable to split: frame is not a child of this one.");
+ }
+
+ $this->revert_counter_increment();
+
+ $node = $this->_frame->get_node();
+ $split = $this->copy($node->cloneNode());
+
+ $style = $this->_frame->get_style();
+ $split_style = $split->get_style();
+
+ // Truncate the box decoration at the split, except for the body
+ if ($node->nodeName !== "body") {
+ // Clear bottom decoration of original frame
+ $style->margin_bottom = 0.0;
+ $style->padding_bottom = 0.0;
+ $style->border_bottom_width = 0.0;
+ $style->border_bottom_left_radius = 0.0;
+ $style->border_bottom_right_radius = 0.0;
+
+ // Clear top decoration of split frame
+ $split_style->margin_top = 0.0;
+ $split_style->padding_top = 0.0;
+ $split_style->border_top_width = 0.0;
+ $split_style->border_top_left_radius = 0.0;
+ $split_style->border_top_right_radius = 0.0;
+ $split_style->page_break_before = "auto";
+ }
+
+ $split_style->text_indent = 0.0;
+ $split_style->counter_reset = "none";
+
+ $this->is_split = true;
+ $split->is_split_off = true;
+ $split->_already_pushed = true;
+
+ $this->get_parent()->insert_child_after($split, $this);
+
+ if ($this instanceof Block) {
+ // Remove the frames that will be moved to the new split node from
+ // the line boxes
+ $this->remove_frames_from_line($child);
+
+ // recalculate the float offsets after paging
+ foreach ($this->get_line_boxes() as $line_box) {
+ $line_box->get_float_offsets();
+ }
+ }
+
+ if (!$forced) {
+ // Reset top margin in case of an unforced page break
+ // https://www.w3.org/TR/CSS21/page.html#allowed-page-breaks
+ $child->get_style()->margin_top = 0.0;
+ }
+
+ // Add $child and all following siblings to the new split node
+ $iter = $child;
+ while ($iter) {
+ $frame = $iter;
+ $iter = $iter->get_next_sibling();
+ $frame->reset();
+ $split->append_child($frame);
+ }
+
+ $this->get_parent()->split($split, $page_break, $forced);
+
+ // Preserve the current counter values. This must be done after the
+ // parent split, as counters get reset on frame reset
+ $split->_counters = $this->_counters;
+ }
+
+ /**
+ * @param array $counters
+ */
+ public function reset_counters(array $counters): void
+ {
+ foreach ($counters as $id => $value) {
+ $this->reset_counter($id, $value);
+ }
+ }
+
+ /**
+ * @param string $id
+ * @param int $value
+ */
+ public function reset_counter(string $id = self::DEFAULT_COUNTER, int $value = 0): void
+ {
+ $this->get_parent()->_counters[$id] = $value;
+ }
+
+ /**
+ * @param array $counters
+ */
+ public function decrement_counters(array $counters): void
+ {
+ foreach ($counters as $id => $increment) {
+ $this->increment_counter($id, $increment * -1);
+ }
+ }
+
+ /**
+ * @param array $counters
+ */
+ public function increment_counters(array $counters): void
+ {
+ foreach ($counters as $id => $increment) {
+ $this->increment_counter($id, $increment);
+ }
+ }
+
+ /**
+ * @param string $id
+ * @param int $increment
+ */
+ public function increment_counter(string $id = self::DEFAULT_COUNTER, int $increment = 1): void
+ {
+ $counter_frame = $this->lookup_counter_frame($id, true);
+ $counter_frame->_counters[$id] += $increment;
+ }
+
+ /**
+ * @param string $id
+ * @param bool $auto_reset Instantiate a new counter if none with the given name is in scope.
+ *
+ * @return AbstractFrameDecorator|null
+ */
+ public function lookup_counter_frame(
+ string $id = self::DEFAULT_COUNTER,
+ bool $auto_reset = false
+ ): ?AbstractFrameDecorator {
+ $f = $this->get_parent();
+
+ while ($f) {
+ if (isset($f->_counters[$id])) {
+ return $f;
+ }
+ $f = $f->get_parent();
+ }
+
+ if ($auto_reset) {
+ $f = $this->get_parent();
+ $f->_counters[$id] = 0;
+ return $f;
+ }
+
+ return null;
+ }
+
+ /**
+ * @param string $id
+ * @param string $type
+ *
+ * @return string
+ *
+ * TODO: What version is the best : this one or the one in ListBullet ?
+ */
+ public function counter_value(string $id = self::DEFAULT_COUNTER, string $type = "decimal"): string
+ {
+ $value = $this->_counters[$id] ?? 0;
+
+ switch ($type) {
+ default:
+ case "decimal":
+ return $value;
+
+ case "decimal-leading-zero":
+ return str_pad($value, 2, "0", STR_PAD_LEFT);
+
+ case "lower-roman":
+ return Helpers::dec2roman($value);
+
+ case "upper-roman":
+ return strtoupper(Helpers::dec2roman($value));
+
+ case "lower-latin":
+ case "lower-alpha":
+ return chr((($value - 1) % 26) + ord('a'));
+
+ case "upper-latin":
+ case "upper-alpha":
+ return chr((($value - 1) % 26) + ord('A'));
+
+ case "lower-greek":
+ return Helpers::unichr($value + 944);
+
+ case "upper-greek":
+ return Helpers::unichr($value + 912);
+ }
+ }
+
+ final function position()
+ {
+ $this->_positioner->position($this);
+ }
+
+ /**
+ * @param float $offset_x
+ * @param float $offset_y
+ * @param bool $ignore_self
+ */
+ final function move(float $offset_x, float $offset_y, bool $ignore_self = false): void
+ {
+ $this->_positioner->move($this, $offset_x, $offset_y, $ignore_self);
+ }
+
+ /**
+ * @param Block|null $block
+ */
+ final function reflow(?Block $block = null)
+ {
+ // Uncomment this to see the frames before they're laid out, instead of
+ // during rendering.
+ //echo $this->_frame; flush();
+ $this->_reflower->reflow($block);
+ }
+
+ /**
+ * @return array
+ */
+ final public function get_min_max_width(): array
+ {
+ return $this->_reflower->get_min_max_width();
+ }
+}
diff --git a/app/libs/dompdf/vendor/dompdf/dompdf/src/FrameDecorator/Block.php b/app/libs/dompdf/vendor/dompdf/dompdf/src/FrameDecorator/Block.php
new file mode 100644
index 0000000..dd95209
--- /dev/null
+++ b/app/libs/dompdf/vendor/dompdf/dompdf/src/FrameDecorator/Block.php
@@ -0,0 +1,256 @@
+_line_boxes = [new LineBox($this)];
+ $this->_cl = 0;
+ $this->dangling_markers = [];
+ }
+
+ function reset()
+ {
+ parent::reset();
+
+ $this->_line_boxes = [new LineBox($this)];
+ $this->_cl = 0;
+ $this->dangling_markers = [];
+ }
+
+ /**
+ * @return LineBox
+ */
+ function get_current_line_box()
+ {
+ return $this->_line_boxes[$this->_cl];
+ }
+
+ /**
+ * @return int
+ */
+ function get_current_line_number()
+ {
+ return $this->_cl;
+ }
+
+ /**
+ * @return LineBox[]
+ */
+ function get_line_boxes()
+ {
+ return $this->_line_boxes;
+ }
+
+ /**
+ * @param int $line_number
+ * @return int
+ */
+ function set_current_line_number($line_number)
+ {
+ $line_boxes_count = count($this->_line_boxes);
+ $cl = max(min($line_number, $line_boxes_count), 0);
+ return ($this->_cl = $cl);
+ }
+
+ /**
+ * @param int $i
+ */
+ function clear_line($i)
+ {
+ if (isset($this->_line_boxes[$i])) {
+ unset($this->_line_boxes[$i]);
+ }
+ }
+
+ /**
+ * @param Frame $frame
+ * @return LineBox|null
+ */
+ public function add_frame_to_line(Frame $frame): ?LineBox
+ {
+ $current_line = $this->_line_boxes[$this->_cl];
+ $frame->set_containing_line($current_line);
+
+ // Inline frames are currently treated as wrappers, and are not actually
+ // added to the line
+ if ($frame instanceof Inline) {
+ return null;
+ }
+
+ $current_line->add_frame($frame);
+
+ $this->increase_line_width($frame->get_margin_width());
+ $this->maximize_line_height($frame->get_margin_height(), $frame);
+
+ // Add any dangling list markers to the first line box if it is inline
+ if ($this->_cl === 0 && $current_line->inline
+ && $this->dangling_markers !== []
+ ) {
+ foreach ($this->dangling_markers as $marker) {
+ $current_line->add_list_marker($marker);
+ $this->maximize_line_height($marker->get_margin_height(), $marker);
+ }
+
+ $this->dangling_markers = [];
+ }
+
+ return $current_line;
+ }
+
+ /**
+ * Remove the given frame and all following frames and lines from the block.
+ *
+ * @param Frame $frame
+ */
+ public function remove_frames_from_line(Frame $frame): void
+ {
+ // Inline frames are not added to line boxes themselves, only their
+ // text frame children
+ $actualFrame = $frame;
+ while ($actualFrame !== null && $actualFrame instanceof Inline) {
+ $actualFrame = $actualFrame->get_first_child();
+ }
+
+ if ($actualFrame === null) {
+ return;
+ }
+
+ // Search backwards through the lines for $frame
+ $frame = $actualFrame;
+ $i = $this->_cl;
+ $j = null;
+
+ while ($i >= 0) {
+ $line = $this->_line_boxes[$i];
+ foreach ($line->get_frames() as $index => $f) {
+ if ($frame === $f) {
+ $j = $index;
+ break 2;
+ }
+ }
+ $i--;
+ }
+
+ if ($j === null) {
+ return;
+ }
+
+ // Remove all lines that follow
+ for ($k = $this->_cl; $k > $i; $k--) {
+ unset($this->_line_boxes[$k]);
+ }
+
+ // Remove the line, if it is empty
+ if ($j > 0) {
+ $line->remove_frames($j);
+ } else {
+ unset($this->_line_boxes[$i]);
+ }
+
+ // Reset array indices
+ $this->_line_boxes = array_values($this->_line_boxes);
+ $this->_cl = count($this->_line_boxes) - 1;
+ }
+
+ /**
+ * @param float $w
+ */
+ public function increase_line_width(float $w): void
+ {
+ $this->_line_boxes[$this->_cl]->w += $w;
+ }
+
+ /**
+ * @param float $val
+ * @param Frame $frame
+ */
+ public function maximize_line_height(float $val, Frame $frame): void
+ {
+ if ($val > $this->_line_boxes[$this->_cl]->h) {
+ $this->_line_boxes[$this->_cl]->tallest_frame = $frame;
+ $this->_line_boxes[$this->_cl]->h = $val;
+ }
+ }
+
+ /**
+ * @param bool $br
+ */
+ public function add_line(bool $br = false): void
+ {
+ $line = $this->_line_boxes[$this->_cl];
+
+ $line->br = $br;
+ $y = $line->y + $line->h;
+
+ $new_line = new LineBox($this, $y);
+
+ $this->_line_boxes[++$this->_cl] = $new_line;
+ }
+
+ /**
+ * @param ListBullet $marker
+ */
+ public function add_dangling_marker(ListBullet $marker): void
+ {
+ $this->dangling_markers[] = $marker;
+ }
+
+ /**
+ * Inherit any dangling markers from the parent block.
+ *
+ * @param Block $block
+ */
+ public function inherit_dangling_markers(self $block): void
+ {
+ if ($block->dangling_markers !== []) {
+ $this->dangling_markers = $block->dangling_markers;
+ $block->dangling_markers = [];
+ }
+ }
+}
diff --git a/app/libs/dompdf/vendor/dompdf/dompdf/src/FrameDecorator/Image.php b/app/libs/dompdf/vendor/dompdf/dompdf/src/FrameDecorator/Image.php
new file mode 100644
index 0000000..bbfb130
--- /dev/null
+++ b/app/libs/dompdf/vendor/dompdf/dompdf/src/FrameDecorator/Image.php
@@ -0,0 +1,120 @@
+get_node();
+ $url = $node->getAttribute("src");
+
+ $debug_png = $dompdf->getOptions()->getDebugPng();
+ if ($debug_png) {
+ print '[__construct ' . $url . ']';
+ }
+
+ list($this->_image_url, /*$type*/, $this->_image_msg) = Cache::resolve_url(
+ $url,
+ $dompdf->getProtocol(),
+ $dompdf->getBaseHost(),
+ $dompdf->getBasePath(),
+ $dompdf->getOptions()
+ );
+
+ if (Cache::is_broken($this->_image_url) && ($alt = $node->getAttribute("alt")) !== "") {
+ $fontMetrics = $dompdf->getFontMetrics();
+ $style = $frame->get_style();
+ $font = $style->font_family;
+ $size = $style->font_size;
+ $word_spacing = $style->word_spacing;
+ $letter_spacing = $style->letter_spacing;
+
+ $style->width = $fontMetrics->getTextWidth($alt, $font, $size, $word_spacing, $letter_spacing);
+ $style->height = $fontMetrics->getFontHeight($font, $size);
+ }
+ }
+
+ /**
+ * Get the intrinsic pixel dimensions of the image.
+ *
+ * @return array Width and height as `float|int`.
+ */
+ public function get_intrinsic_dimensions(): array
+ {
+ [$width, $height] = Helpers::dompdf_getimagesize($this->_image_url, $this->_dompdf->getHttpContext());
+
+ return [$width, $height];
+ }
+
+ /**
+ * Resample the given pixel length according to dpi.
+ *
+ * @param float|int $length
+ * @return float
+ */
+ public function resample($length): float
+ {
+ $dpi = $this->_dompdf->getOptions()->getDpi();
+ return ($length * 72) / $dpi;
+ }
+
+ /**
+ * Return the image's url
+ *
+ * @return string The url of this image
+ */
+ function get_image_url()
+ {
+ return $this->_image_url;
+ }
+
+ /**
+ * Return the image's error message
+ *
+ * @return string The image's error message
+ */
+ function get_image_msg()
+ {
+ return $this->_image_msg;
+ }
+
+}
diff --git a/app/libs/dompdf/vendor/dompdf/dompdf/src/FrameDecorator/Inline.php b/app/libs/dompdf/vendor/dompdf/dompdf/src/FrameDecorator/Inline.php
new file mode 100644
index 0000000..668d795
--- /dev/null
+++ b/app/libs/dompdf/vendor/dompdf/dompdf/src/FrameDecorator/Inline.php
@@ -0,0 +1,121 @@
+get_style();
+ $font = $style->font_family;
+ $size = $style->font_size;
+ $fontHeight = $this->_dompdf->getFontMetrics()->getFontHeight($font, $size);
+
+ return ($style->line_height / ($size > 0 ? $size : 1)) * $fontHeight;
+ }
+
+ public function split(?Frame $child = null, bool $page_break = false, bool $forced = false): void
+ {
+ if (is_null($child)) {
+ $this->get_parent()->split($this, $page_break, $forced);
+ return;
+ }
+
+ if ($child->get_parent() !== $this) {
+ throw new Exception("Unable to split: frame is not a child of this one.");
+ }
+
+ $this->revert_counter_increment();
+ $node = $this->_frame->get_node();
+ $split = $this->copy($node->cloneNode());
+
+ $style = $this->_frame->get_style();
+ $split_style = $split->get_style();
+
+ // Unset the current node's right style properties
+ $style->margin_right = 0.0;
+ $style->padding_right = 0.0;
+ $style->border_right_width = 0.0;
+ $style->border_top_right_radius = 0.0;
+ $style->border_bottom_right_radius = 0.0;
+
+ // Unset the split node's left style properties since we don't want them
+ // to propagate
+ $split_style->margin_left = 0.0;
+ $split_style->padding_left = 0.0;
+ $split_style->border_left_width = 0.0;
+ $split_style->border_top_left_radius = 0.0;
+ $split_style->border_bottom_left_radius = 0.0;
+
+ // If this is a generated node don't propagate the content style
+ if ($split->get_node()->nodeName == "dompdf_generated") {
+ $split_style->content = "normal";
+ }
+
+ //On continuation of inline element on next line,
+ //don't repeat non-horizontally repeatable background images
+ //See e.g. in testcase image_variants, long descriptions
+ if (($url = $style->background_image) && $url !== "none"
+ && ($repeat = $style->background_repeat) && $repeat !== "repeat" && $repeat !== "repeat-x"
+ ) {
+ $split_style->background_image = "none";
+ }
+
+ $this->get_parent()->insert_child_after($split, $this);
+
+ // Add $child and all following siblings to the new split node
+ $iter = $child;
+ while ($iter) {
+ $frame = $iter;
+ $iter = $iter->get_next_sibling();
+ $frame->reset();
+ $split->append_child($frame);
+ }
+
+ $parent = $this->get_parent();
+
+ if ($page_break) {
+ $parent->split($split, $page_break, $forced);
+ } elseif ($parent instanceof Inline) {
+ $parent->split($split);
+ }
+ }
+
+}
diff --git a/app/libs/dompdf/vendor/dompdf/dompdf/src/FrameDecorator/ListBullet.php b/app/libs/dompdf/vendor/dompdf/dompdf/src/FrameDecorator/ListBullet.php
new file mode 100644
index 0000000..703f467
--- /dev/null
+++ b/app/libs/dompdf/vendor/dompdf/dompdf/src/FrameDecorator/ListBullet.php
@@ -0,0 +1,117 @@
+_frame->get_style();
+
+ if ($style->list_style_type === "none") {
+ return 0.0;
+ }
+
+ return $style->font_size * self::BULLET_SIZE;
+ }
+
+ /**
+ * Get the height of the bullet symbol.
+ *
+ * @return float
+ */
+ public function get_height(): float
+ {
+ $style = $this->_frame->get_style();
+
+ if ($style->list_style_type === "none") {
+ return 0.0;
+ }
+
+ return $style->font_size * self::BULLET_SIZE;
+ }
+
+ /**
+ * Get the width of the bullet, including indentation.
+ */
+ public function get_margin_width(): float
+ {
+ $style = $this->get_style();
+
+ if ($style->list_style_type === "none") {
+ return 0.0;
+ }
+
+ return $style->font_size * (self::BULLET_SIZE + self::MARKER_INDENT);
+ }
+
+ /**
+ * Get the line height for the bullet.
+ *
+ * This increases the height of the corresponding line box when necessary.
+ */
+ public function get_margin_height(): float
+ {
+ $style = $this->get_style();
+
+ if ($style->list_style_type === "none") {
+ return 0.0;
+ }
+
+ // TODO: This is a copy of `FrameDecorator\Text::get_margin_height()`
+ // Would be nice to properly refactor that at some point
+ $font = $style->font_family;
+ $size = $style->font_size;
+ $fontHeight = $this->_dompdf->getFontMetrics()->getFontHeight($font, $size);
+
+ return ($style->line_height / ($size > 0 ? $size : 1)) * $fontHeight;
+ }
+}
diff --git a/app/libs/dompdf/vendor/dompdf/dompdf/src/FrameDecorator/ListBulletImage.php b/app/libs/dompdf/vendor/dompdf/dompdf/src/FrameDecorator/ListBulletImage.php
new file mode 100644
index 0000000..df6c105
--- /dev/null
+++ b/app/libs/dompdf/vendor/dompdf/dompdf/src/FrameDecorator/ListBulletImage.php
@@ -0,0 +1,111 @@
+get_style();
+ $url = $style->list_style_image;
+ $frame->get_node()->setAttribute("src", $url);
+ $this->_img = new Image($frame, $dompdf);
+ parent::__construct($this->_img, $dompdf);
+
+ $url = $this->_img->get_image_url();
+
+ if (Cache::is_broken($url)) {
+ $this->_width = parent::get_width();
+ $this->_height = parent::get_height();
+ } else {
+ // Resample the bullet image to be consistent with 'auto' sized images
+ [$width, $height] = $this->_img->get_intrinsic_dimensions();
+ $this->_width = $this->_img->resample($width);
+ $this->_height = $this->_img->resample($height);
+ }
+ }
+
+ public function get_width(): float
+ {
+ return $this->_width;
+ }
+
+ public function get_height(): float
+ {
+ return $this->_height;
+ }
+
+ public function get_margin_width(): float
+ {
+ $style = $this->get_style();
+ return $this->_width + $style->font_size * self::MARKER_INDENT;
+ }
+
+ public function get_margin_height(): float
+ {
+ $fontMetrics = $this->_dompdf->getFontMetrics();
+ $style = $this->get_style();
+ $font = $style->font_family;
+ $size = $style->font_size;
+ $fontHeight = $fontMetrics->getFontHeight($font, $size);
+ $baseline = $fontMetrics->getFontBaseline($font, $size);
+
+ // This is the same factor as used in
+ // `FrameDecorator\Text::get_margin_height()`
+ $f = $style->line_height / ($size > 0 ? $size : 1);
+
+ // FIXME: Tries to approximate replacing the space above the font
+ // baseline with the image
+ return $f * ($fontHeight - $baseline) + $this->_height;
+ }
+
+ /**
+ * Return image url
+ *
+ * @return string
+ */
+ function get_image_url()
+ {
+ return $this->_img->get_image_url();
+ }
+}
diff --git a/app/libs/dompdf/vendor/dompdf/dompdf/src/FrameDecorator/NullFrameDecorator.php b/app/libs/dompdf/vendor/dompdf/dompdf/src/FrameDecorator/NullFrameDecorator.php
new file mode 100644
index 0000000..f083816
--- /dev/null
+++ b/app/libs/dompdf/vendor/dompdf/dompdf/src/FrameDecorator/NullFrameDecorator.php
@@ -0,0 +1,33 @@
+_frame->get_style();
+ $style->width = 0;
+ $style->height = 0;
+ $style->margin = 0;
+ $style->padding = 0;
+ }
+}
diff --git a/app/libs/dompdf/vendor/dompdf/dompdf/src/FrameDecorator/Page.php b/app/libs/dompdf/vendor/dompdf/dompdf/src/FrameDecorator/Page.php
new file mode 100644
index 0000000..374cc97
--- /dev/null
+++ b/app/libs/dompdf/vendor/dompdf/dompdf/src/FrameDecorator/Page.php
@@ -0,0 +1,767 @@
+_page_full = false;
+ $this->_in_table = 0;
+ $this->bottom_page_edge = null;
+ }
+
+ /**
+ * Set the renderer used for this pdf
+ *
+ * @param Renderer $renderer the renderer to use
+ */
+ function set_renderer($renderer)
+ {
+ $this->_renderer = $renderer;
+ }
+
+ /**
+ * Return the renderer used for this pdf
+ *
+ * @return Renderer
+ */
+ function get_renderer()
+ {
+ return $this->_renderer;
+ }
+
+ /**
+ * Calculate the bottom edge of the page area after margins have been
+ * applied for the current page.
+ */
+ public function calculate_bottom_page_edge(): void
+ {
+ [, , , $cbh] = $this->get_containing_block();
+ $style = $this->get_style();
+ $margin_bottom = (float) $style->length_in_pt($style->margin_bottom, $cbh);
+
+ $this->bottom_page_edge = $cbh - $margin_bottom;
+ }
+
+ /**
+ * Returns true if the page is full and is no longer accepting frames.
+ *
+ * @return bool
+ */
+ function is_full()
+ {
+ return $this->_page_full;
+ }
+
+ /**
+ * Start a new page by resetting the full flag.
+ */
+ function next_page()
+ {
+ $this->_floating_frames = [];
+ $this->_renderer->new_page();
+ $this->_page_full = false;
+ }
+
+ /**
+ * Indicate to the page that a table is currently being reflowed.
+ */
+ function table_reflow_start()
+ {
+ $this->_in_table++;
+ }
+
+ /**
+ * Indicate to the page that table reflow is finished.
+ */
+ function table_reflow_end()
+ {
+ $this->_in_table--;
+ }
+
+ /**
+ * Return whether we are currently in a nested table or not
+ *
+ * @return bool
+ */
+ function in_nested_table()
+ {
+ return $this->_in_table > 1;
+ }
+
+ /**
+ * Check if a forced page break is required before $frame. This uses the
+ * frame's page_break_before property as well as the preceding frame's
+ * page_break_after property.
+ *
+ * @link http://www.w3.org/TR/CSS21/page.html#forced
+ *
+ * @param AbstractFrameDecorator $frame the frame to check
+ *
+ * @return bool true if a page break occurred
+ */
+ function check_forced_page_break(Frame $frame)
+ {
+ // Skip check if page is already split and for the body
+ if ($this->_page_full || $frame->get_node()->nodeName === "body") {
+ return false;
+ }
+
+ // If the frame is fixed-position or has a fixed-position parent
+ // ignore the forced page break
+ if ($frame->get_style()->is_absolute()) {
+ return false;
+ }
+ $p = $frame;
+ while ($p = $p->get_parent()) {
+ if ($p->get_style()->position === "fixed") {
+ return false;
+ }
+ }
+
+ $page_breaks = ["always", "left", "right"];
+ $style = $frame->get_style();
+
+ if (($frame->is_block_level() || $style->display === "table-row")
+ && in_array($style->page_break_before, $page_breaks, true)
+ ) {
+ // Prevent cascading splits
+ $frame->split(null, true, true);
+ $style->page_break_before = "auto";
+ $this->_page_full = true;
+ $frame->_already_pushed = true;
+
+ return true;
+ }
+
+ // Find the preceding block-level sibling (or table row). Inline
+ // elements are treated as if wrapped in an anonymous block container
+ // here. See https://www.w3.org/TR/CSS21/visuren.html#anonymous-block-level
+ $prev = $frame->get_prev_sibling();
+ while ($prev && (($prev->is_text_node() && $prev->get_node()->nodeValue === "")
+ || $prev->get_node()->nodeName === "bullet")
+ ) {
+ $prev = $prev->get_prev_sibling();
+ }
+
+ if ($prev && ($prev->is_block_level() || $prev->get_style()->display === "table-row") && !$prev->get_style()->is_absolute()) {
+ if (in_array($prev->get_style()->page_break_after, $page_breaks, true)) {
+ // Prevent cascading splits
+ $frame->split(null, true, true);
+ $prev->get_style()->page_break_after = "auto";
+ $this->_page_full = true;
+ $frame->_already_pushed = true;
+
+ return true;
+ }
+
+ $prev_last_child = $prev->get_last_child();
+ while ($prev_last_child && (($prev_last_child->is_text_node() && $prev_last_child->get_node()->nodeValue === "")
+ || $prev_last_child->get_node()->nodeName === "bullet")
+ ) {
+ $prev_last_child = $prev_last_child->get_prev_sibling();
+ }
+
+ if ($prev_last_child
+ && $prev_last_child->is_block_level()
+ && in_array($prev_last_child->get_style()->page_break_after, $page_breaks, true)
+ ) {
+ $frame->split(null, true, true);
+ $prev_last_child->get_style()->page_break_after = "auto";
+ $this->_page_full = true;
+ $frame->_already_pushed = true;
+
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Check for a gap between the top content edge of a frame and its child
+ * content.
+ *
+ * Additionally, the top margin, border, and padding of the frame must fit
+ * on the current page.
+ *
+ * @param float $childPos The top margin or line-box edge of the child content.
+ * @param Frame $frame The parent frame to check.
+ * @return bool
+ */
+ protected function hasGap(float $childPos, Frame $frame): bool
+ {
+ $style = $frame->get_style();
+ $cbw = $frame->get_containing_block("w");
+ $contentEdge = $frame->get_position("y") + (float) $style->length_in_pt([
+ $style->margin_top,
+ $style->border_top_width,
+ $style->padding_top
+ ], $cbw);
+
+ return Helpers::lengthGreater($childPos, $contentEdge)
+ && Helpers::lengthLessOrEqual($contentEdge, $this->bottom_page_edge);
+ }
+
+ /**
+ * Determine if a page break is allowed before $frame
+ * http://www.w3.org/TR/CSS21/page.html#allowed-page-breaks
+ *
+ * In the normal flow, page breaks can occur at the following places:
+ *
+ * 1. In the vertical margin between block boxes. When an
+ * unforced page break occurs here, the used values of the
+ * relevant 'margin-top' and 'margin-bottom' properties are set
+ * to '0'. When a forced page break occurs here, the used value
+ * of the relevant 'margin-bottom' property is set to '0'; the
+ * relevant 'margin-top' used value may either be set to '0' or
+ * retained.
+ * 2. Between line boxes inside a block container box.
+ * 3. Between the content edge of a block container box and the
+ * outer edges of its child content (margin edges of block-level
+ * children or line box edges for inline-level children) if there
+ * is a (non-zero) gap between them.
+ *
+ * These breaks are subject to the following rules:
+ *
+ * * Rule A: Breaking at (1) is allowed only if the
+ * 'page-break-after' and 'page-break-before' properties of all
+ * the elements generating boxes that meet at this margin allow
+ * it, which is when at least one of them has the value
+ * 'always', 'left', or 'right', or when all of them are 'auto'.
+ *
+ * * Rule B: However, if all of them are 'auto' and a common
+ * ancestor of all the elements has a 'page-break-inside' value
+ * of 'avoid', then breaking here is not allowed.
+ *
+ * * Rule C: Breaking at (2) is allowed only if the number of line
+ * boxes between the break and the start of the enclosing block
+ * box is the value of 'orphans' or more, and the number of line
+ * boxes between the break and the end of the box is the value
+ * of 'widows' or more.
+ *
+ * * Rule D: In addition, breaking at (2) or (3) is allowed only
+ * if the 'page-break-inside' property of the element and all
+ * its ancestors is 'auto'.
+ *
+ * If the above does not provide enough break points to keep content
+ * from overflowing the page boxes, then rules A, B and D are
+ * dropped in order to find additional breakpoints.
+ *
+ * If that still does not lead to sufficient break points, rule C is
+ * dropped as well, to find still more break points.
+ *
+ * We also allow breaks between table rows.
+ *
+ * @param AbstractFrameDecorator $frame the frame to check
+ *
+ * @return bool true if a break is allowed, false otherwise
+ */
+ protected function _page_break_allowed(Frame $frame)
+ {
+ Helpers::dompdf_debug("page-break", "_page_break_allowed(" . $frame->get_node()->nodeName . ")");
+ $display = $frame->get_style()->display;
+
+ // Block Frames (1):
+ if ($frame->is_block_level() || $display === "-dompdf-image") {
+
+ // Avoid breaks within table-cells
+ if ($this->_in_table > ($display === "table" ? 1 : 0)) {
+ Helpers::dompdf_debug("page-break", "In table: " . $this->_in_table);
+
+ return false;
+ }
+
+ // Rule A
+ if ($frame->get_style()->page_break_before === "avoid") {
+ Helpers::dompdf_debug("page-break", "before: avoid");
+
+ return false;
+ }
+
+ // Find the preceding block-level sibling. Inline elements are
+ // treated as if wrapped in an anonymous block container here. See
+ // https://www.w3.org/TR/CSS21/visuren.html#anonymous-block-level
+ $prev = $frame->get_prev_sibling();
+ while ($prev && (($prev->is_text_node() && $prev->get_node()->nodeValue === "")
+ || $prev->get_node()->nodeName === "bullet")
+ ) {
+ $prev = $prev->get_prev_sibling();
+ }
+
+ // Does the previous element allow a page break after?
+ if ($prev && ($prev->is_block_level() || $prev->get_style()->display === "-dompdf-image")
+ && $prev->get_style()->page_break_after === "avoid"
+ ) {
+ Helpers::dompdf_debug("page-break", "after: avoid");
+
+ return false;
+ }
+
+ // Rules B & D
+ $parent = $frame->get_parent();
+ $p = $parent;
+ while ($p) {
+ if ($p->get_style()->page_break_inside === "avoid") {
+ Helpers::dompdf_debug("page-break", "parent->inside: avoid");
+
+ return false;
+ }
+ $p = $p->find_block_parent();
+ }
+
+ // To prevent cascading page breaks when a top-level element has
+ // page-break-inside: avoid, ensure that at least one frame is
+ // on the page before splitting.
+ if ($parent->get_node()->nodeName === "body" && !$prev) {
+ // We are the body's first child
+ Helpers::dompdf_debug("page-break", "Body's first child.");
+
+ return false;
+ }
+
+ // Check for a possible type (3) break
+ if (!$prev && $parent && !$this->hasGap($frame->get_position("y"), $parent)) {
+ Helpers::dompdf_debug("page-break", "First block-level frame, no gap");
+
+ return false;
+ }
+
+ Helpers::dompdf_debug("page-break", "block: break allowed");
+
+ return true;
+
+ } // Inline frames (2):
+ else {
+ if ($frame->is_inline_level()) {
+
+ // Avoid breaks within table-cells
+ if ($this->_in_table) {
+ Helpers::dompdf_debug("page-break", "In table: " . $this->_in_table);
+
+ return false;
+ }
+
+ // Rule C
+ $block_parent = $frame->find_block_parent();
+ $parent_style = $block_parent->get_style();
+ $line = $block_parent->get_current_line_box();
+ $line_count = count($block_parent->get_line_boxes());
+ $line_number = $frame->get_containing_line() && empty($line->get_frames())
+ ? $line_count - 1
+ : $line_count;
+
+ // The line number of the frame can be less than the current
+ // number of line boxes, in case we are backtracking. As long as
+ // we are not checking for widows yet, just checking against the
+ // number of line boxes is sufficient in most cases, though.
+ if ($line_number <= $parent_style->orphans) {
+ Helpers::dompdf_debug("page-break", "orphans");
+
+ return false;
+ }
+
+ // FIXME: Checking widows is tricky without having laid out the
+ // remaining line boxes. Just ignore it for now...
+
+ // Rule D
+ $p = $block_parent;
+ while ($p) {
+ if ($p->get_style()->page_break_inside === "avoid") {
+ Helpers::dompdf_debug("page-break", "parent->inside: avoid");
+
+ return false;
+ }
+ $p = $p->find_block_parent();
+ }
+
+ // To prevent cascading page breaks when a top-level element has
+ // page-break-inside: avoid, ensure that at least one frame with
+ // some content is on the page before splitting.
+ $prev = $frame->get_prev_sibling();
+ while ($prev && ($prev->is_text_node() && trim($prev->get_node()->nodeValue) == "")) {
+ $prev = $prev->get_prev_sibling();
+ }
+
+ if ($block_parent->get_node()->nodeName === "body" && !$prev) {
+ // We are the body's first child
+ Helpers::dompdf_debug("page-break", "Body's first child.");
+
+ return false;
+ }
+
+ Helpers::dompdf_debug("page-break", "inline: break allowed");
+
+ return true;
+
+ // Table-rows
+ } else {
+ if ($display === "table-row") {
+
+ // If this is a nested table, prevent the page from breaking
+ if ($this->_in_table > 1) {
+ Helpers::dompdf_debug("page-break", "table: nested table");
+
+ return false;
+ }
+
+ // Rule A (table row)
+ if ($frame->get_style()->page_break_before === "avoid") {
+ Helpers::dompdf_debug("page-break", "before: avoid");
+
+ return false;
+ }
+
+ // Find the preceding row
+ $prev = $frame->get_prev_sibling();
+
+ if (!$prev) {
+ $prev_group = $frame->get_parent()->get_prev_sibling();
+
+ if ($prev_group
+ && in_array($prev_group->get_style()->display, Table::ROW_GROUPS, true)
+ ) {
+ $prev = $prev_group->get_last_child();
+ }
+ }
+
+ // Check if a page break is allowed after the preceding row
+ if ($prev && $prev->get_style()->page_break_after === "avoid") {
+ Helpers::dompdf_debug("page-break", "after: avoid");
+
+ return false;
+ }
+
+ // Avoid breaking before the first row of a table
+ if (!$prev) {
+ Helpers::dompdf_debug("page-break", "table: first-row");
+
+ return false;
+ }
+
+ // Rule B (table row)
+ // Check if the page_break_inside property is not 'avoid'
+ // for the parent table or any of its ancestors
+ $table = Table::find_parent_table($frame);
+ if ($table === null) {
+ throw new Exception("Parent table not found for table row");
+ }
+
+ $p = $table;
+ while ($p) {
+ if ($p->get_style()->page_break_inside === "avoid") {
+ Helpers::dompdf_debug("page-break", "parent->inside: avoid");
+
+ return false;
+ }
+ $p = $p->find_block_parent();
+ }
+
+ Helpers::dompdf_debug("page-break", "table-row: break allowed");
+
+ return true;
+ } else {
+ if (in_array($display, Table::ROW_GROUPS, true)) {
+
+ // Disallow breaks at row-groups: only split at row boundaries
+ return false;
+
+ } else {
+ Helpers::dompdf_debug("page-break", "? " . $display);
+
+ return false;
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Check if $frame will fit on the page. If the frame does not fit,
+ * the frame tree is modified so that a page break occurs in the
+ * correct location.
+ *
+ * @param AbstractFrameDecorator $frame the frame to check
+ *
+ * @return bool
+ */
+ function check_page_break(Frame $frame)
+ {
+ if ($this->_page_full || $frame->_already_pushed
+ // Never check for breaks on empty text nodes
+ || ($frame->is_text_node() && $frame->get_node()->nodeValue === "")
+ ) {
+ return false;
+ }
+
+ $p = $frame;
+ do {
+ $display = $p->get_style()->display;
+ if ($display == "table-row") {
+ if ($p->_already_pushed) { return false; }
+ }
+ } while ($p = $p->get_parent());
+
+ // If the frame is absolute or fixed it shouldn't break
+ $p = $frame;
+ do {
+ if ($p->is_absolute()) {
+ return false;
+ }
+ } while ($p = $p->get_parent());
+
+ $margin_height = $frame->get_margin_height();
+
+ // Determine the frame's maximum y value
+ $max_y = (float)$frame->get_position("y") + $margin_height;
+
+ // If a split is to occur here, then the bottom margins & paddings of all
+ // parents of $frame must fit on the page as well:
+ $p = $frame->get_parent();
+ while ($p && $p !== $this) {
+ $cbw = $p->get_containing_block("w");
+ $max_y += (float) $p->get_style()->computed_bottom_spacing($cbw);
+ $p = $p->get_parent();
+ }
+
+ // Check if $frame flows off the page
+ if (Helpers::lengthLessOrEqual($max_y, $this->bottom_page_edge)) {
+ // no: do nothing
+ return false;
+ }
+
+ Helpers::dompdf_debug("page-break", "check_page_break");
+ Helpers::dompdf_debug("page-break", "in_table: " . $this->_in_table);
+
+ // yes: determine page break location
+ $iter = $frame;
+ $flg = false;
+ $pushed_flg = false;
+
+ $in_table = $this->_in_table;
+
+ Helpers::dompdf_debug("page-break", "Starting search");
+ while ($iter) {
+ // echo "\nbacktrack: " .$iter->get_node()->nodeName ." ".spl_object_hash($iter->get_node()). "";
+ if ($iter === $this) {
+ Helpers::dompdf_debug("page-break", "reached root.");
+ // We've reached the root in our search. Just split at $frame.
+ break;
+ }
+
+ if ($iter->_already_pushed) {
+ $pushed_flg = true;
+ } elseif ($this->_page_break_allowed($iter)) {
+ Helpers::dompdf_debug("page-break", "break allowed, splitting.");
+ $iter->split(null, true);
+ $this->_page_full = true;
+ $this->_in_table = $in_table;
+ $iter->_already_pushed = true;
+ $frame->_already_pushed = true;
+
+ return true;
+ }
+
+ if (!$flg && $next = $iter->get_last_child()) {
+ Helpers::dompdf_debug("page-break", "following last child.");
+
+ if ($next->is_table()) {
+ $this->_in_table++;
+ }
+
+ $iter = $next;
+ $pushed_flg = false;
+ continue;
+ }
+
+ if ($pushed_flg) {
+ // The frame was already pushed, avoid breaking on a previous page
+ break;
+ }
+
+ $next = $iter->get_prev_sibling();
+ // Skip empty text nodes
+ while ($next && $next->is_text_node() && $next->get_node()->nodeValue === "") {
+ $next = $next->get_prev_sibling();
+ }
+
+ if ($next) {
+ Helpers::dompdf_debug("page-break", "following prev sibling.");
+
+ if ($next->is_table() && !$iter->is_table()) {
+ $this->_in_table++;
+ } elseif (!$next->is_table() && $iter->is_table()) {
+ $this->_in_table--;
+ }
+
+ $iter = $next;
+ $flg = false;
+ continue;
+ }
+
+ if ($next = $iter->get_parent()) {
+ Helpers::dompdf_debug("page-break", "following parent.");
+
+ if ($iter->is_table()) {
+ $this->_in_table--;
+ }
+
+ $iter = $next;
+ $flg = true;
+ continue;
+ }
+
+ break;
+ }
+
+ $this->_in_table = $in_table;
+
+ // No valid page break found. Just break at $frame.
+ Helpers::dompdf_debug("page-break", "no valid break found, just splitting.");
+
+ // If we are in a table, backtrack to the nearest top-level table row
+ if ($this->_in_table) {
+ $iter = $frame;
+ while ($iter && $iter->get_style()->display !== "table-row" && $iter->get_style()->display !== 'table-row-group' && $iter->_already_pushed === false) {
+ $iter = $iter->get_parent();
+ }
+
+ if ($iter) {
+ $iter->split(null, true);
+ $iter->_already_pushed = true;
+ } else {
+ return false;
+ }
+ } else {
+ $frame->split(null, true);
+ }
+
+ $this->_page_full = true;
+ $frame->_already_pushed = true;
+
+ return true;
+ }
+
+ //........................................................................
+
+ public function split(?Frame $child = null, bool $page_break = false, bool $forced = false): void
+ {
+ // Do nothing
+ }
+
+ /**
+ * Add a floating frame
+ *
+ * @param Frame $frame
+ */
+ function add_floating_frame(Frame $frame)
+ {
+ array_unshift($this->_floating_frames, $frame);
+ }
+
+ /**
+ * @return Frame[]
+ */
+ function get_floating_frames()
+ {
+ return $this->_floating_frames;
+ }
+
+ /**
+ * @param $key
+ */
+ public function remove_floating_frame($key)
+ {
+ unset($this->_floating_frames[$key]);
+ }
+
+ /**
+ * @param Frame $child
+ * @return int|mixed
+ */
+ public function get_lowest_float_offset(Frame $child)
+ {
+ $style = $child->get_style();
+ $side = $style->clear;
+ $float = $style->float;
+
+ $y = 0;
+
+ if ($float === "none") {
+ foreach ($this->_floating_frames as $key => $frame) {
+ if ($side === "both" || $frame->get_style()->float === $side) {
+ $y = max($y, $frame->get_position("y") + $frame->get_margin_height());
+ }
+ $this->remove_floating_frame($key);
+ }
+ }
+
+ if ($y > 0) {
+ $y++; // add 1px buffer from float
+ }
+
+ return $y;
+ }
+}
diff --git a/app/libs/dompdf/vendor/dompdf/dompdf/src/FrameDecorator/Table.php b/app/libs/dompdf/vendor/dompdf/dompdf/src/FrameDecorator/Table.php
new file mode 100644
index 0000000..5ba8dda
--- /dev/null
+++ b/app/libs/dompdf/vendor/dompdf/dompdf/src/FrameDecorator/Table.php
@@ -0,0 +1,344 @@
+_cellmap = new Cellmap($this);
+
+ $style = $frame->get_style();
+ if ($style->table_layout === "fixed" && $style->width !== "auto") {
+ $this->_cellmap->set_layout_fixed(true);
+ }
+
+ $this->_headers = [];
+ $this->_footers = [];
+ }
+
+ public function reset()
+ {
+ parent::reset();
+ $this->_cellmap->reset();
+ $this->_headers = [];
+ $this->_footers = [];
+ $this->_reflower->reset();
+ }
+
+ //........................................................................
+
+ /**
+ * Split the table at $row. $row and all subsequent rows will be
+ * added to the clone. This method is overridden in order to remove
+ * frames from the cellmap properly.
+ */
+ public function split(?Frame $child = null, bool $page_break = false, bool $forced = false): void
+ {
+ if (is_null($child)) {
+ parent::split($child, $page_break, $forced);
+ return;
+ }
+
+ // If $child is a header or if it is the first non-header row, do
+ // not duplicate headers, simply move the table to the next page.
+ if (count($this->_headers)
+ && !in_array($child, $this->_headers, true)
+ && !in_array($child->get_prev_sibling(), $this->_headers, true)
+ ) {
+ $first_header = null;
+
+ // Insert copies of the table headers before $child
+ foreach ($this->_headers as $header) {
+
+ $new_header = $header->deep_copy();
+
+ if (is_null($first_header)) {
+ $first_header = $new_header;
+ }
+
+ $this->insert_child_before($new_header, $child);
+ }
+
+ parent::split($first_header, $page_break, $forced);
+
+ } elseif (in_array($child->get_style()->display, self::ROW_GROUPS, true)) {
+
+ // Individual rows should have already been handled
+ parent::split($child, $page_break, $forced);
+
+ } else {
+
+ $iter = $child;
+
+ while ($iter) {
+ $this->_cellmap->remove_row($iter);
+ $iter = $iter->get_next_sibling();
+ }
+
+ parent::split($child, $page_break, $forced);
+ }
+ }
+
+ public function copy(DOMNode $node)
+ {
+ $deco = parent::copy($node);
+
+ // In order to keep columns' widths through pages
+ $deco->_cellmap->set_columns($this->_cellmap->get_columns());
+ $deco->_cellmap->lock_columns();
+
+ return $deco;
+ }
+
+ /**
+ * Static function to locate the parent table of a frame
+ *
+ * @param Frame $frame
+ *
+ * @return Table the table that is an ancestor of $frame
+ */
+ public static function find_parent_table(Frame $frame)
+ {
+ while ($frame = $frame->get_parent()) {
+ if ($frame->is_table()) {
+ break;
+ }
+ }
+
+ return $frame;
+ }
+
+ /**
+ * Return this table's Cellmap
+ *
+ * @return Cellmap
+ */
+ public function get_cellmap()
+ {
+ return $this->_cellmap;
+ }
+
+ //........................................................................
+
+ /**
+ * Check for text nodes between valid table children that only contain white
+ * space, except if white space is to be preserved.
+ *
+ * @param AbstractFrameDecorator $frame
+ *
+ * @return bool
+ */
+ private function isEmptyTextNode(AbstractFrameDecorator $frame): bool
+ {
+ // This is based on the white-space pattern in `FrameReflower\Text`,
+ // i.e. only match on collapsible white space
+ $wsPattern = '/^[^\S\xA0\x{202F}\x{2007}]*$/u';
+ $validChildOrNull = function ($frame) {
+ return $frame === null
+ || in_array($frame->get_style()->display, self::VALID_CHILDREN, true);
+ };
+
+ return $frame instanceof Text
+ && !$frame->is_pre()
+ && preg_match($wsPattern, $frame->get_text())
+ && $validChildOrNull($frame->get_prev_sibling())
+ && $validChildOrNull($frame->get_next_sibling());
+ }
+
+ /**
+ * Restructure tree so that the table has the correct structure. Misplaced
+ * children are appropriately wrapped in anonymous row groups, rows, and
+ * cells.
+ *
+ * https://www.w3.org/TR/CSS21/tables.html#anonymous-boxes
+ */
+ public function normalize(): void
+ {
+ $column_caption = ["table-column-group", "table-column", "table-caption"];
+ $children = iterator_to_array($this->get_children());
+ $tbody = null;
+
+ foreach ($children as $child) {
+ $display = $child->get_style()->display;
+
+ if (in_array($display, self::ROW_GROUPS, true)) {
+ // Reset anonymous tbody
+ $tbody = null;
+
+ // Add headers and footers
+ if ($display === "table-header-group") {
+ $this->_headers[] = $child;
+ } elseif ($display === "table-footer-group") {
+ $this->_footers[] = $child;
+ }
+ continue;
+ }
+
+ if (in_array($display, $column_caption, true)) {
+ continue;
+ }
+
+ // Remove empty text nodes between valid children
+ if ($this->isEmptyTextNode($child)) {
+ $this->remove_child($child);
+ continue;
+ }
+
+ // Catch consecutive misplaced frames within a single anonymous group
+ if ($tbody === null) {
+ $tbody = $this->create_anonymous_child("tbody", "table-row-group");
+ $this->insert_child_before($tbody, $child);
+ }
+
+ $tbody->append_child($child);
+ }
+
+ // Handle empty table: Make sure there is at least one row group
+ if (!$this->get_first_child()) {
+ $tbody = $this->create_anonymous_child("tbody", "table-row-group");
+ $this->append_child($tbody);
+ }
+
+ foreach ($this->get_children() as $child) {
+ $display = $child->get_style()->display;
+
+ if (in_array($display, self::ROW_GROUPS, true)) {
+ $this->normalizeRowGroup($child);
+ }
+ }
+ }
+
+ private function normalizeRowGroup(AbstractFrameDecorator $frame): void
+ {
+ $children = iterator_to_array($frame->get_children());
+ $tr = null;
+
+ foreach ($children as $child) {
+ $display = $child->get_style()->display;
+
+ if ($display === "table-row") {
+ // Reset anonymous tr
+ $tr = null;
+ continue;
+ }
+
+ // Remove empty text nodes between valid children
+ if ($this->isEmptyTextNode($child)) {
+ $frame->remove_child($child);
+ continue;
+ }
+
+ // Catch consecutive misplaced frames within a single anonymous row
+ if ($tr === null) {
+ $tr = $frame->create_anonymous_child("tr", "table-row");
+ $frame->insert_child_before($tr, $child);
+ }
+
+ $tr->append_child($child);
+ }
+
+ // Handle empty row group: Make sure there is at least one row
+ if (!$frame->get_first_child()) {
+ $tr = $frame->create_anonymous_child("tr", "table-row");
+ $frame->append_child($tr);
+ }
+
+ foreach ($frame->get_children() as $child) {
+ $this->normalizeRow($child);
+ }
+ }
+
+ private function normalizeRow(AbstractFrameDecorator $frame): void
+ {
+ $children = iterator_to_array($frame->get_children());
+ $td = null;
+
+ foreach ($children as $child) {
+ $display = $child->get_style()->display;
+
+ if ($display === "table-cell") {
+ // Reset anonymous td
+ $td = null;
+ continue;
+ }
+
+ // Remove empty text nodes between valid children
+ if ($this->isEmptyTextNode($child)) {
+ $frame->remove_child($child);
+ continue;
+ }
+
+ // Catch consecutive misplaced frames within a single anonymous cell
+ if ($td === null) {
+ $td = $frame->create_anonymous_child("td", "table-cell");
+ $frame->insert_child_before($td, $child);
+ }
+
+ $td->append_child($child);
+ }
+
+ // Handle empty row: Make sure there is at least one cell
+ if (!$frame->get_first_child()) {
+ $td = $frame->create_anonymous_child("td", "table-cell");
+ $frame->append_child($td);
+ }
+ }
+}
diff --git a/app/libs/dompdf/vendor/dompdf/dompdf/src/FrameDecorator/TableCell.php b/app/libs/dompdf/vendor/dompdf/dompdf/src/FrameDecorator/TableCell.php
new file mode 100644
index 0000000..7d06b55
--- /dev/null
+++ b/app/libs/dompdf/vendor/dompdf/dompdf/src/FrameDecorator/TableCell.php
@@ -0,0 +1,111 @@
+content_height = 0.0;
+ }
+
+ function reset()
+ {
+ parent::reset();
+ $this->content_height = 0.0;
+ }
+
+ /**
+ * @return float
+ */
+ public function get_content_height(): float
+ {
+ return $this->content_height;
+ }
+
+ /**
+ * @param float $height
+ */
+ public function set_content_height(float $height): void
+ {
+ $this->content_height = $height;
+ }
+
+ /**
+ * @param float $height
+ */
+ public function set_cell_height(float $height): void
+ {
+ $style = $this->get_style();
+ $v_space = (float)$style->length_in_pt(
+ [
+ $style->margin_top,
+ $style->padding_top,
+ $style->border_top_width,
+ $style->border_bottom_width,
+ $style->padding_bottom,
+ $style->margin_bottom
+ ],
+ (float)$style->length_in_pt($style->height)
+ );
+
+ $new_height = $height - $v_space;
+ $style->set_used("height", $new_height);
+
+ if ($new_height > $this->content_height) {
+ $y_offset = 0;
+
+ // Adjust our vertical alignment
+ switch ($style->vertical_align) {
+ default:
+ case "baseline":
+ // FIXME: this isn't right
+
+ case "top":
+ // Don't need to do anything
+ return;
+
+ case "middle":
+ $y_offset = ($new_height - $this->content_height) / 2;
+ break;
+
+ case "bottom":
+ $y_offset = $new_height - $this->content_height;
+ break;
+ }
+
+ if ($y_offset) {
+ // Move our children
+ foreach ($this->get_line_boxes() as $line) {
+ foreach ($line->get_frames() as $frame) {
+ $frame->move(0, $y_offset);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/app/libs/dompdf/vendor/dompdf/dompdf/src/FrameDecorator/TableRow.php b/app/libs/dompdf/vendor/dompdf/dompdf/src/FrameDecorator/TableRow.php
new file mode 100644
index 0000000..ba985c9
--- /dev/null
+++ b/app/libs/dompdf/vendor/dompdf/dompdf/src/FrameDecorator/TableRow.php
@@ -0,0 +1,28 @@
+get_parent();
+ $cellmap = $parent->get_cellmap();
+ $iter = $child;
+
+ while ($iter) {
+ $cellmap->remove_row($iter);
+ $iter = $iter->get_next_sibling();
+ }
+
+ // Remove all subsequent row groups from the cellmap
+ $iter = $this->get_next_sibling();
+
+ while ($iter) {
+ $cellmap->remove_row_group($iter);
+ $iter = $iter->get_next_sibling();
+ }
+
+ // If we are splitting at the first child remove the
+ // table-row-group from the cellmap as well
+ if ($child === $this->get_first_child()) {
+ $cellmap->remove_row_group($this);
+ parent::split(null, $page_break, $forced);
+ return;
+ }
+
+ $cellmap->update_row_group($this, $child->get_prev_sibling());
+ parent::split($child, $page_break, $forced);
+ }
+}
diff --git a/app/libs/dompdf/vendor/dompdf/dompdf/src/FrameDecorator/Text.php b/app/libs/dompdf/vendor/dompdf/dompdf/src/FrameDecorator/Text.php
new file mode 100644
index 0000000..894be3f
--- /dev/null
+++ b/app/libs/dompdf/vendor/dompdf/dompdf/src/FrameDecorator/Text.php
@@ -0,0 +1,279 @@
+is_text_node()) {
+ throw new Exception("Text_Decorator can only be applied to #text nodes.");
+ }
+
+ parent::__construct($frame, $dompdf);
+ $this->text_spacing = 0.0;
+ }
+
+ /**
+ * Trim trailing white space from the frame text.
+ */
+ public function trim_trailing_ws(): void
+ {
+ $frame = $this->_frame;
+ $text = $this->get_text();
+ $trailing = mb_substr($text, -1, null, "UTF-8");
+
+ // White space is always collapsed to the standard space character
+ // currently, so only handle that for now
+ if ($trailing === " ") {
+ $this->trailingWs = $trailing;
+ $this->set_text(mb_substr($text, 0, -1, "UTF-8"));
+ $this->recalculate_width();
+ }
+ }
+
+ function reset()
+ {
+ parent::reset();
+ $this->text_spacing = 0.0;
+ $this->mapped_font = null;
+
+ // Restore trimmed trailing white space, as the frame will go through
+ // another reflow and line breaks might be different after a split
+ if ($this->trailingWs !== null) {
+ $text = $this->get_text();
+ $this->set_text($text . $this->trailingWs);
+ $this->trailingWs = null;
+ }
+ }
+
+ // Accessor methods
+
+ /**
+ * @return float
+ */
+ public function get_text_spacing(): float
+ {
+ return $this->text_spacing;
+ }
+
+ /**
+ * @return string
+ */
+ function get_text()
+ {
+ // FIXME: this should be in a child class (and is incorrect)
+// if ( $this->_frame->get_style()->content !== "normal" ) {
+// $this->_frame->get_node()->data = $this->_frame->get_style()->content;
+// $this->_frame->get_style()->content = "normal";
+// }
+
+// Helpers::pre_r("---");
+// $style = $this->_frame->get_style();
+// var_dump($text = $this->_frame->get_node()->data);
+// var_dump($asc = utf8_decode($text));
+// for ($i = 0; $i < strlen($asc); $i++)
+// Helpers::pre_r("$i: " . $asc[$i] . " - " . ord($asc[$i]));
+// Helpers::pre_r("width: " . $this->_dompdf->getFontMetrics()->getTextWidth($text, $style->font_family, $style->font_size));
+
+ return $this->_frame->get_node()->data;
+ }
+
+ //........................................................................
+
+ /**
+ * Vertical padding, border, and margin do not apply when determining the
+ * height for inline frames.
+ *
+ * http://www.w3.org/TR/CSS21/visudet.html#inline-non-replaced
+ *
+ * The vertical padding, border and margin of an inline, non-replaced box
+ * start at the top and bottom of the content area, not the
+ * 'line-height'. But only the 'line-height' is used to calculate the
+ * height of the line box.
+ *
+ * @return float
+ */
+ public function get_margin_height(): float
+ {
+ // This function is also called in add_frame_to_line() and is used to
+ // determine the line height
+ $style = $this->get_style();
+ $font = $style->font_family;
+ $size = $style->font_size;
+ $fontHeight = $this->_dompdf->getFontMetrics()->getFontHeight($font, $size);
+
+ return ($style->line_height / ($size > 0 ? $size : 1)) * $fontHeight;
+ }
+
+ public function get_padding_box(): array
+ {
+ $style = $this->_frame->get_style();
+ $pb = $this->_frame->get_padding_box();
+ $pb[3] = $pb["h"] = (float) $style->length_in_pt($style->height);
+ return $pb;
+ }
+
+ /**
+ * @param float $spacing
+ */
+ public function set_text_spacing(float $spacing): void
+ {
+ $this->text_spacing = $spacing;
+ $this->recalculate_width();
+ }
+
+ /**
+ * Recalculate the text width
+ *
+ * @return float
+ */
+ public function recalculate_width(): float
+ {
+ $fontMetrics = $this->_dompdf->getFontMetrics();
+ $style = $this->get_style();
+ $text = $this->get_text();
+ $font = $style->font_family;
+ $size = $style->font_size;
+ $word_spacing = $this->text_spacing + $style->word_spacing;
+ $letter_spacing = $style->letter_spacing;
+ $text_width = $fontMetrics->getTextWidth($text, $font, $size, $word_spacing, $letter_spacing);
+
+ $style->set_used("width", $text_width);
+ return $text_width;
+ }
+
+ // Text manipulation methods
+
+ /**
+ * Split the text in this frame at the offset specified. The remaining
+ * text is added as a sibling frame following this one and is returned.
+ *
+ * @param int $offset
+ * @param bool $split_parent Whether to split parent inline frames.
+ *
+ * @return Text|null
+ */
+ function split_text(int $offset, bool $split_parent = true): ?self
+ {
+ if ($offset === 0) {
+ return null;
+ }
+
+ $split = $this->_frame->get_node()->splitText($offset);
+ if ($split === false) {
+ return null;
+ }
+
+ /** @var Text */
+ $deco = $this->copy($split);
+ $style = $this->_frame->get_style();
+ $split_style = $deco->get_style();
+
+ if ($this->mapped_font !== null) {
+ $split_style->set_used("font_family", $this->mapped_font);
+ $deco->mapped_font = $this->mapped_font;
+ }
+
+ // Clear decoration widths at the split point. They might have been
+ // copied from the parent frame during inline reflow
+ $style->margin_right = 0.0;
+ $style->padding_right = 0.0;
+ $style->border_right_width = 0.0;
+
+ $split_style->margin_left = 0.0;
+ $split_style->padding_left = 0.0;
+ $split_style->border_left_width = 0.0;
+
+ $p = $this->get_parent();
+ $p->insert_child_after($deco, $this, false);
+
+ if ($split_parent && $p instanceof Inline) {
+ $p->split($deco);
+ }
+
+ return $deco;
+ }
+
+ /**
+ * @param int $offset
+ * @param int $count
+ */
+ function delete_text($offset, $count)
+ {
+ $this->_frame->get_node()->deleteData($offset, $count);
+ }
+
+ /**
+ * @param string $text
+ */
+ function set_text($text)
+ {
+ $this->_frame->get_node()->data = $text;
+ }
+
+ /**
+ * Determines the optimal font that applies to the frame and splits
+ * the frame where the optimal font changes.
+ */
+ function apply_font_mapping(): void
+ {
+ if ($this->mapped_font !== null) {
+ return;
+ }
+
+ $fontMetrics = $this->_dompdf->getFontMetrics();
+ $style = $this->get_style();
+ $families = $style->get_font_family_computed();
+ $subtype = $fontMetrics->getType($style->font_weight . ' ' . $style->font_style);
+ $charMapping = $fontMetrics->mapTextToFonts($this->get_text(), $families, $subtype, 1);
+
+ if (isset($charMapping[0])) {
+ if ($charMapping[0]["length"] !== 0) {
+ $this->split_text($charMapping[0]["length"], false);
+ }
+ $mapped_font = $charMapping[0]["font"];
+ if ($mapped_font !== null) {
+ $style->set_used("font_family", $mapped_font);
+ $this->mapped_font = $mapped_font;
+ }
+ }
+ }
+}
diff --git a/app/libs/dompdf/vendor/dompdf/dompdf/src/FrameReflower/AbstractFrameReflower.php b/app/libs/dompdf/vendor/dompdf/dompdf/src/FrameReflower/AbstractFrameReflower.php
new file mode 100644
index 0000000..7f0cb51
--- /dev/null
+++ b/app/libs/dompdf/vendor/dompdf/dompdf/src/FrameReflower/AbstractFrameReflower.php
@@ -0,0 +1,607 @@
+_frame = $frame;
+ $this->_min_max_child_cache = null;
+ $this->_min_max_cache = null;
+ }
+
+ /**
+ * @return Dompdf
+ */
+ function get_dompdf()
+ {
+ return $this->_frame->get_dompdf();
+ }
+
+ public function reset(): void
+ {
+ $this->_min_max_child_cache = null;
+ $this->_min_max_cache = null;
+ }
+
+ /**
+ * Determine the actual containing block for absolute and fixed position.
+ *
+ * https://www.w3.org/TR/CSS21/visudet.html#containing-block-details
+ */
+ protected function determine_absolute_containing_block(): void
+ {
+ $frame = $this->_frame;
+ $style = $frame->get_style();
+
+ switch ($style->position) {
+ case "absolute":
+ $parent = $frame->find_positioned_parent();
+ if ($parent !== $frame->get_root()) {
+ $parent_style = $parent->get_style();
+ $parent_padding_box = $parent->get_padding_box();
+ //FIXME: an accurate measure of the positioned parent height
+ // is not possible until reflow has completed;
+ // we'll fall back to the parent's containing block,
+ // which is wrong for auto-height parents
+ if ($parent_style->height === "auto") {
+ $parent_containing_block = $parent->get_containing_block();
+ $containing_block_height = $parent_containing_block["h"] -
+ (float)$parent_style->length_in_pt([
+ $parent_style->margin_top,
+ $parent_style->margin_bottom,
+ $parent_style->border_top_width,
+ $parent_style->border_bottom_width
+ ], $parent_containing_block["w"]);
+ } else {
+ $containing_block_height = $parent_padding_box["h"];
+ }
+ $frame->set_containing_block($parent_padding_box["x"], $parent_padding_box["y"], $parent_padding_box["w"], $containing_block_height);
+ break;
+ }
+ case "fixed":
+ $root = $frame->get_root();
+ $parent = $frame->get_parent();
+ do {
+ $parents_parent = $parent->get_parent();
+ if ($parents_parent == $root) {
+ break;
+ }
+ $parent = $parents_parent;
+ } while ($parent);
+ $initial_cb = $parent->get_containing_block();
+ $frame->set_containing_block($initial_cb["x"], $initial_cb["y"], $initial_cb["w"], $initial_cb["h"]);
+ break;
+ default:
+ // Nothing to do, containing block already set via parent
+ break;
+ }
+ }
+
+ /**
+ * Collapse frames margins
+ * http://www.w3.org/TR/CSS21/box.html#collapsing-margins
+ */
+ protected function _collapse_margins(): void
+ {
+ $frame = $this->_frame;
+
+ // Margins of float/absolutely positioned/inline-level elements do not collapse
+ if (!$frame->is_in_flow() || $frame->is_inline_level()
+ || $frame->get_root() === $frame || $frame->get_parent() === $frame->get_root()
+ ) {
+ return;
+ }
+
+ $cb = $frame->get_containing_block();
+ $style = $frame->get_style();
+
+ $t = $style->length_in_pt($style->margin_top, $cb["w"]);
+ $b = $style->length_in_pt($style->margin_bottom, $cb["w"]);
+
+ // Handle 'auto' values
+ if ($t === "auto") {
+ $style->set_used("margin_top", 0.0);
+ $t = 0.0;
+ }
+
+ if ($b === "auto") {
+ $style->set_used("margin_bottom", 0.0);
+ $b = 0.0;
+ }
+
+ // Collapse vertical margins:
+ $n = $frame->get_next_sibling();
+ if ( $n && !($n->is_block_level() && $n->is_in_flow()) ) {
+ while ($n = $n->get_next_sibling()) {
+ if ($n->is_block_level() && $n->is_in_flow()) {
+ break;
+ }
+
+ if (!$n->get_first_child()) {
+ $n = null;
+ break;
+ }
+ }
+ }
+
+ if ($n) {
+ $n_style = $n->get_style();
+ $n_t = (float)$n_style->length_in_pt($n_style->margin_top, $cb["w"]);
+
+ $b = $this->get_collapsed_margin_length($b, $n_t);
+ $style->set_used("margin_bottom", $b);
+ $n_style->set_used("margin_top", 0.0);
+ }
+
+ // Collapse our first child's margin, if there is no border or padding
+ if ($style->border_top_width == 0 && $style->length_in_pt($style->padding_top) == 0) {
+ $f = $this->_frame->get_first_child();
+ if ( $f && !($f->is_block_level() && $f->is_in_flow()) ) {
+ while ($f = $f->get_next_sibling()) {
+ if ($f->is_block_level() && $f->is_in_flow()) {
+ break;
+ }
+
+ if (!$f->get_first_child()) {
+ $f = null;
+ break;
+ }
+ }
+ }
+
+ // Margins are collapsed only between block-level boxes
+ if ($f) {
+ $f_style = $f->get_style();
+ $f_t = (float)$f_style->length_in_pt($f_style->margin_top, $cb["w"]);
+
+ $t = $this->get_collapsed_margin_length($t, $f_t);
+ $style->set_used("margin_top", $t);
+ $f_style->set_used("margin_top", 0.0);
+ }
+ }
+
+ // Collapse our last child's margin, if there is no border or padding
+ if ($style->border_bottom_width == 0 && $style->length_in_pt($style->padding_bottom) == 0) {
+ $l = $this->_frame->get_last_child();
+ if ( $l && !($l->is_block_level() && $l->is_in_flow()) ) {
+ while ($l = $l->get_prev_sibling()) {
+ if ($l->is_block_level() && $l->is_in_flow()) {
+ break;
+ }
+
+ if (!$l->get_last_child()) {
+ $l = null;
+ break;
+ }
+ }
+ }
+
+ // Margins are collapsed only between block-level boxes
+ if ($l) {
+ $l_style = $l->get_style();
+ $l_b = (float)$l_style->length_in_pt($l_style->margin_bottom, $cb["w"]);
+
+ $b = $this->get_collapsed_margin_length($b, $l_b);
+ $style->set_used("margin_bottom", $b);
+ $l_style->set_used("margin_bottom", 0.0);
+ }
+ }
+ }
+
+ /**
+ * Get the combined (collapsed) length of two adjoining margins.
+ *
+ * See http://www.w3.org/TR/CSS21/box.html#collapsing-margins.
+ *
+ * @param float $l1
+ * @param float $l2
+ *
+ * @return float
+ */
+ private function get_collapsed_margin_length(float $l1, float $l2): float
+ {
+ if ($l1 < 0 && $l2 < 0) {
+ return min($l1, $l2); // min(x, y) = - max(abs(x), abs(y)), if x < 0 && y < 0
+ }
+
+ if ($l1 < 0 || $l2 < 0) {
+ return $l1 + $l2; // x + y = x - abs(y), if y < 0
+ }
+
+ return max($l1, $l2);
+ }
+
+ /**
+ * Handle relative positioning according to
+ * https://www.w3.org/TR/CSS21/visuren.html#relative-positioning.
+ *
+ * @param AbstractFrameDecorator $frame The frame to handle.
+ */
+ protected function position_relative(AbstractFrameDecorator $frame): void
+ {
+ $style = $frame->get_style();
+
+ if ($style->position === "relative") {
+ $cb = $frame->get_containing_block();
+ $top = $style->length_in_pt($style->top, $cb["h"]);
+ $right = $style->length_in_pt($style->right, $cb["w"]);
+ $bottom = $style->length_in_pt($style->bottom, $cb["h"]);
+ $left = $style->length_in_pt($style->left, $cb["w"]);
+
+ // FIXME RTL case:
+ // if ($left !== "auto" && $right !== "auto") $left = -$right;
+ if ($left === "auto" && $right === "auto") {
+ $left = 0;
+ } elseif ($left === "auto") {
+ $left = -$right;
+ }
+
+ if ($top === "auto" && $bottom === "auto") {
+ $top = 0;
+ } elseif ($top === "auto") {
+ $top = -$bottom;
+ }
+
+ $frame->move($left, $top);
+ }
+ }
+
+ /**
+ * @param Block|null $block
+ */
+ abstract function reflow(?Block $block = null);
+
+ /**
+ * Resolve the `min-width` property.
+ *
+ * Resolves to 0 if not set or if a percentage and the containing-block
+ * width is not defined.
+ *
+ * @param float|null $cbw Width of the containing block.
+ *
+ * @return float
+ */
+ protected function resolve_min_width(?float $cbw): float
+ {
+ $style = $this->_frame->get_style();
+ $min_width = $style->min_width;
+
+ return $min_width !== "auto"
+ ? $style->length_in_pt($min_width, $cbw ?? 0)
+ : 0.0;
+ }
+
+ /**
+ * Resolve the `max-width` property.
+ *
+ * Resolves to `INF` if not set or if a percentage and the containing-block
+ * width is not defined.
+ *
+ * @param float|null $cbw Width of the containing block.
+ *
+ * @return float
+ */
+ protected function resolve_max_width(?float $cbw): float
+ {
+ $style = $this->_frame->get_style();
+ $max_width = $style->max_width;
+
+ return $max_width !== "none"
+ ? $style->length_in_pt($max_width, $cbw ?? INF)
+ : INF;
+ }
+
+ /**
+ * Resolve the `min-height` property.
+ *
+ * Resolves to 0 if not set or if a percentage and the containing-block
+ * height is not defined.
+ *
+ * @param float|null $cbh Height of the containing block.
+ *
+ * @return float
+ */
+ protected function resolve_min_height(?float $cbh): float
+ {
+ $style = $this->_frame->get_style();
+ $min_height = $style->min_height;
+
+ return $min_height !== "auto"
+ ? $style->length_in_pt($min_height, $cbh ?? 0)
+ : 0.0;
+ }
+
+ /**
+ * Resolve the `max-height` property.
+ *
+ * Resolves to `INF` if not set or if a percentage and the containing-block
+ * height is not defined.
+ *
+ * @param float|null $cbh Height of the containing block.
+ *
+ * @return float
+ */
+ protected function resolve_max_height(?float $cbh): float
+ {
+ $style = $this->_frame->get_style();
+ $max_height = $style->max_height;
+
+ return $max_height !== "none"
+ ? $style->length_in_pt($style->max_height, $cbh ?? INF)
+ : INF;
+ }
+
+ /**
+ * Get the minimum and maximum preferred width of the contents of the frame,
+ * as requested by its children.
+ *
+ * @return array A two-element array of min and max width.
+ */
+ public function get_min_max_child_width(): array
+ {
+ if (!is_null($this->_min_max_child_cache)) {
+ return $this->_min_max_child_cache;
+ }
+
+ $low = [];
+ $high = [];
+
+ for ($iter = $this->_frame->get_children(); $iter->valid(); $iter->next()) {
+ $inline_min = 0;
+ $inline_max = 0;
+
+ // Add all adjacent inline widths together to calculate max width
+ while ($iter->valid() && ($iter->current()->is_inline_level() || $iter->current()->get_style()->display === "-dompdf-image")) {
+ /** @var AbstractFrameDecorator */
+ $child = $iter->current();
+ $child->get_reflower()->_set_content();
+ $minmax = $child->get_min_max_width();
+
+ if (in_array($child->get_style()->white_space, ["pre", "nowrap"], true)) {
+ $inline_min += $minmax["min"];
+ } else {
+ $low[] = $minmax["min"];
+ }
+
+ $inline_max += $minmax["max"];
+ $iter->next();
+ }
+
+ if ($inline_min > 0) {
+ $low[] = $inline_min;
+ }
+ if ($inline_max > 0) {
+ $high[] = $inline_max;
+ }
+
+ // Skip children with absolute position
+ if ($iter->valid()) {
+ /** @var AbstractFrameDecorator */
+ $child = $iter->current();
+ $child->get_reflower()->_set_content();
+ if (!$iter->current()->is_absolute()) {
+ list($low[], $high[]) = $child->get_min_max_width();
+ }
+ }
+ }
+
+ $min = count($low) ? max($low) : 0;
+ $max = count($high) ? max($high) : 0;
+
+ return $this->_min_max_child_cache = [$min, $max];
+ }
+
+ /**
+ * Get the minimum and maximum preferred content-box width of the frame.
+ *
+ * @return array A two-element array of min and max width.
+ */
+ public function get_min_max_content_width(): array
+ {
+ return $this->get_min_max_child_width();
+ }
+
+ /**
+ * Get the minimum and maximum preferred border-box width of the frame.
+ *
+ * Required for shrink-to-fit width calculation, as used in automatic table
+ * layout, absolute positioning, float and inline-block. This provides a
+ * basic implementation. Child classes should override this or
+ * `get_min_max_content_width` as necessary.
+ *
+ * @return array An array `[0 => min, 1 => max, "min" => min, "max" => max]`
+ * of min and max width.
+ */
+ public function get_min_max_width(): array
+ {
+ if (!is_null($this->_min_max_cache)) {
+ return $this->_min_max_cache;
+ }
+
+ $style = $this->_frame->get_style();
+ [$min, $max] = $this->get_min_max_content_width();
+
+ // Account for margins, borders, and padding
+ $dims = [
+ $style->padding_left,
+ $style->padding_right,
+ $style->border_left_width,
+ $style->border_right_width,
+ $style->margin_left,
+ $style->margin_right
+ ];
+
+ // The containing block is not defined yet, treat percentages as 0
+ $delta = (float) $style->length_in_pt($dims, 0);
+ $min += $delta;
+ $max += $delta;
+
+ return $this->_min_max_cache = [$min, $max, "min" => $min, "max" => $max];
+ }
+
+ /**
+ * Resolves the `content` property to string.
+ *
+ * https://www.w3.org/TR/CSS21/generate.html#content
+ *
+ * @return string The resulting string
+ */
+ protected function resolve_content(): ?string
+ {
+ $frame = $this->_frame;
+ $style = $frame->get_style();
+ $content = $style->content;
+
+ if ($content === "normal" || $content === "none") {
+ return null;
+ }
+
+ $quotes = $style->quotes;
+ $text = "";
+
+ foreach ($content as $val) {
+ if ($val instanceof StringPart) {
+ $text .= $val->string;
+ }
+
+ elseif ($val instanceof OpenQuote) {
+ // FIXME: Take quotation depth into account
+ if ($quotes !== "none" && isset($quotes[0][0])) {
+ $text .= $quotes[0][0];
+ }
+ }
+
+ elseif ($val instanceof CloseQuote) {
+ // FIXME: Take quotation depth into account
+ if ($quotes !== "none" && isset($quotes[0][1])) {
+ $text .= $quotes[0][1];
+ }
+ }
+
+ elseif ($val instanceof NoOpenQuote) {
+ // FIXME: Increment quotation depth
+ }
+
+ elseif ($val instanceof NoCloseQuote) {
+ // FIXME: Decrement quotation depth
+ }
+
+ elseif ($val instanceof Attr) {
+ $text .= $frame->get_parent()->get_node()->getAttribute($val->attribute);
+ }
+
+ elseif ($val instanceof Counter) {
+ $p = $frame->lookup_counter_frame($val->name, true);
+ $text .= $p->counter_value($val->name, $val->style);
+ }
+
+ elseif ($val instanceof Counters) {
+ $p = $frame->lookup_counter_frame($val->name, true);
+ $tmp = [];
+ while ($p) {
+ array_unshift($tmp, $p->counter_value($val->name, $val->style));
+ $p = $p->lookup_counter_frame($val->name);
+ }
+ $text .= implode($val->string, $tmp);
+ }
+ }
+
+ return $text;
+ }
+
+ /**
+ * Handle counters and set generated content if the frame is a
+ * generated-content frame.
+ */
+ protected function _set_content(): void
+ {
+ $frame = $this->_frame;
+
+ if ($frame->content_set) {
+ return;
+ }
+
+ $style = $frame->get_style();
+
+ if (($reset = $style->counter_reset) !== "none") {
+ $frame->reset_counters($reset);
+ }
+
+ if (($increment = $style->counter_increment) !== "none") {
+ $frame->increment_counters($increment);
+ }
+
+ if ($frame->get_node()->nodeName === "dompdf_generated") {
+ $content = $this->resolve_content();
+
+ if ($content !== null) {
+ $node = $frame->get_node()->ownerDocument->createTextNode($content);
+
+ $new_style = $style->get_stylesheet()->create_style();
+ $new_style->inherit($style);
+
+ $new_frame = new Frame($node);
+ $new_frame->set_style($new_style);
+
+ Factory::decorate_frame($new_frame, $frame->get_dompdf(), $frame->get_root());
+ $frame->append_child($new_frame);
+ }
+ }
+
+ $frame->content_set = true;
+ }
+}
diff --git a/app/libs/dompdf/vendor/dompdf/dompdf/src/FrameReflower/Block.php b/app/libs/dompdf/vendor/dompdf/dompdf/src/FrameReflower/Block.php
new file mode 100644
index 0000000..45db9fd
--- /dev/null
+++ b/app/libs/dompdf/vendor/dompdf/dompdf/src/FrameReflower/Block.php
@@ -0,0 +1,948 @@
+_frame;
+ $style = $frame->get_style();
+ $absolute = $frame->is_absolute();
+
+ $cb = $frame->get_containing_block();
+ $w = $cb["w"];
+
+ $rm = $style->length_in_pt($style->margin_right, $w);
+ $lm = $style->length_in_pt($style->margin_left, $w);
+
+ $left = $style->length_in_pt($style->left, $w);
+ $right = $style->length_in_pt($style->right, $w);
+
+ // Handle 'auto' values
+ $dims = [$style->border_left_width,
+ $style->border_right_width,
+ $style->padding_left,
+ $style->padding_right,
+ $width !== "auto" ? $width : 0,
+ $rm !== "auto" ? $rm : 0,
+ $lm !== "auto" ? $lm : 0];
+
+ // absolutely positioned boxes take the 'left' and 'right' properties into account
+ if ($absolute) {
+ $dims[] = $left !== "auto" ? $left : 0;
+ $dims[] = $right !== "auto" ? $right : 0;
+ }
+
+ $sum = (float)$style->length_in_pt($dims, $w);
+
+ // Compare to the containing block
+ $diff = $w - $sum;
+
+ if ($absolute) {
+ // Absolutely positioned
+ // http://www.w3.org/TR/CSS21/visudet.html#abs-non-replaced-width
+
+ if ($width === "auto" || $left === "auto" || $right === "auto") {
+ // "all of the three are 'auto'" logic + otherwise case
+ if ($lm === "auto") {
+ $lm = 0;
+ }
+ if ($rm === "auto") {
+ $rm = 0;
+ }
+
+ $block_parent = $frame->find_block_parent();
+ $parent_content = $block_parent->get_content_box();
+ $line = $block_parent->get_current_line_box();
+
+ // TODO: This is the in-flow inline position. Use the in-flow
+ // block position if the original display type is block-level
+ $inflow_x = $parent_content["x"] - $cb["x"] + $line->left + $line->w;
+
+ if ($width === "auto" && $left === "auto" && $right === "auto") {
+ // rule 3, per instruction preceding rule set
+ // shrink-to-fit width
+ $left = $inflow_x;
+ [$min, $max] = $this->get_min_max_child_width();
+ $width = min(max($min, $diff - $left), $max);
+ $right = $diff - $left - $width;
+ } elseif ($width === "auto" && $left === "auto") {
+ // rule 1
+ // shrink-to-fit width
+ [$min, $max] = $this->get_min_max_child_width();
+ $width = min(max($min, $diff), $max);
+ $left = $diff - $width;
+ } elseif ($width === "auto" && $right === "auto") {
+ // rule 3
+ // shrink-to-fit width
+ [$min, $max] = $this->get_min_max_child_width();
+ $width = min(max($min, $diff), $max);
+ $right = $diff - $width;
+ } elseif ($left === "auto" && $right === "auto") {
+ // rule 2
+ $left = $inflow_x;
+ $right = $diff - $left;
+ } elseif ($left === "auto") {
+ // rule 4
+ $left = $diff;
+ } elseif ($width === "auto") {
+ // rule 5
+ $width = max($diff, 0);
+ } else {
+ // $right === "auto"
+ // rule 6
+ $right = $diff;
+ }
+ } else {
+ // "none of the three are 'auto'" logic described in paragraph preceding the rules
+ if ($diff >= 0) {
+ if ($lm === "auto" && $rm === "auto") {
+ $lm = $rm = $diff / 2;
+ } elseif ($lm === "auto") {
+ $lm = $diff;
+ } elseif ($rm === "auto") {
+ $rm = $diff;
+ }
+ } else {
+ // over-constrained, solve for right
+ $right = $right + $diff;
+
+ if ($lm === "auto") {
+ $lm = 0;
+ }
+ if ($rm === "auto") {
+ $rm = 0;
+ }
+ }
+ }
+ } elseif ($style->float !== "none" || $style->display === "inline-block") {
+ // Shrink-to-fit width for float and inline block
+ // https://www.w3.org/TR/CSS21/visudet.html#float-width
+ // https://www.w3.org/TR/CSS21/visudet.html#inlineblock-width
+
+ if ($width === "auto") {
+ [$min, $max] = $this->get_min_max_child_width();
+ $width = min(max($min, $diff), $max);
+ }
+ if ($lm === "auto") {
+ $lm = 0;
+ }
+ if ($rm === "auto") {
+ $rm = 0;
+ }
+ } else {
+ // Block-level, normal flow
+ // https://www.w3.org/TR/CSS21/visudet.html#blockwidth
+
+ if ($diff >= 0) {
+ // Find auto properties and get them to take up the slack
+ if ($width === "auto") {
+ $width = $diff;
+
+ if ($lm === "auto") {
+ $lm = 0;
+ }
+ if ($rm === "auto") {
+ $rm = 0;
+ }
+ } elseif ($lm === "auto" && $rm === "auto") {
+ $lm = $rm = $diff / 2;
+ } elseif ($lm === "auto") {
+ $lm = $diff;
+ } elseif ($rm === "auto") {
+ $rm = $diff;
+ }
+ } else {
+ // We are over constrained--set margin-right to the difference
+ $rm = (float) $rm + $diff;
+
+ if ($width === "auto") {
+ $width = 0;
+ }
+ if ($lm === "auto") {
+ $lm = 0;
+ }
+ }
+ }
+
+ return [
+ "width" => $width,
+ "margin_left" => $lm,
+ "margin_right" => $rm,
+ "left" => $left,
+ "right" => $right,
+ ];
+ }
+
+ /**
+ * Call the above function, but resolve max/min widths
+ *
+ * @throws Exception
+ * @return array
+ */
+ protected function _calculate_restricted_width()
+ {
+ $frame = $this->_frame;
+ $style = $frame->get_style();
+ $cb = $frame->get_containing_block();
+
+ if (!isset($cb["w"])) {
+ throw new Exception("Box property calculation requires containing block width");
+ }
+
+ $width = $style->length_in_pt($style->width, $cb["w"]);
+
+ $values = $this->_calculate_width($width);
+ $margin_left = $values["margin_left"];
+ $margin_right = $values["margin_right"];
+ $width = $values["width"];
+ $left = $values["left"];
+ $right = $values["right"];
+
+ // Handle min/max width
+ // https://www.w3.org/TR/CSS21/visudet.html#min-max-widths
+ $min_width = $this->resolve_min_width($cb["w"]);
+ $max_width = $this->resolve_max_width($cb["w"]);
+
+ if ($width > $max_width) {
+ $values = $this->_calculate_width($max_width);
+ $margin_left = $values["margin_left"];
+ $margin_right = $values["margin_right"];
+ $width = $values["width"];
+ $left = $values["left"];
+ $right = $values["right"];
+ }
+
+ if ($width < $min_width) {
+ $values = $this->_calculate_width($min_width);
+ $margin_left = $values["margin_left"];
+ $margin_right = $values["margin_right"];
+ $width = $values["width"];
+ $left = $values["left"];
+ $right = $values["right"];
+ }
+
+ return [$width, $margin_left, $margin_right, $left, $right];
+ }
+
+ /**
+ * Determine the unrestricted height of content within the block
+ * not by adding each line's height, but by getting the last line's position.
+ * This because lines could have been pushed lower by a clearing element.
+ *
+ * @return float
+ */
+ protected function _calculate_content_height(): float
+ {
+ $height = 0.0;
+ $lines = $this->_frame->get_line_boxes();
+ if (count($lines) > 0) {
+ $last_line = end($lines);
+ $content_box = $this->_frame->get_content_box();
+ $height = $last_line->y + $last_line->h - $content_box["y"];
+ }
+ return $height;
+ }
+
+ /**
+ * Determine the frame's restricted height
+ *
+ * @return array
+ */
+ protected function _calculate_restricted_height()
+ {
+ $frame = $this->_frame;
+ $style = $frame->get_style();
+ $content_height = $this->_calculate_content_height();
+ $cb = $frame->get_containing_block();
+
+ $height = $style->length_in_pt($style->height, $cb["h"]);
+ $margin_top = $style->length_in_pt($style->margin_top, $cb["w"]);
+ $margin_bottom = $style->length_in_pt($style->margin_bottom, $cb["w"]);
+
+ $top = $style->length_in_pt($style->top, $cb["h"]);
+ $bottom = $style->length_in_pt($style->bottom, $cb["h"]);
+
+ if ($frame->is_absolute()) {
+ // Absolutely positioned
+ // http://www.w3.org/TR/CSS21/visudet.html#abs-non-replaced-height
+
+ $h_dims = [
+ $top !== "auto" ? $top : 0,
+ $height !== "auto" ? $height : 0,
+ $bottom !== "auto" ? $bottom : 0
+ ];
+ $w_dims = [
+ $style->margin_top !== "auto" ? $style->margin_top : 0,
+ $style->padding_top,
+ $style->border_top_width,
+ $style->border_bottom_width,
+ $style->padding_bottom,
+ $style->margin_bottom !== "auto" ? $style->margin_bottom : 0
+ ];
+
+ $sum = (float)$style->length_in_pt($h_dims, $cb["h"])
+ + (float)$style->length_in_pt($w_dims, $cb["w"]);
+
+ $diff = $cb["h"] - $sum;
+
+ if ($height === "auto" || $top === "auto" || $bottom === "auto") {
+ // "all of the three are 'auto'" logic + otherwise case
+ if ($margin_top === "auto") {
+ $margin_top = 0;
+ }
+ if ($margin_bottom === "auto") {
+ $margin_bottom = 0;
+ }
+
+ $block_parent = $frame->find_block_parent();
+ $current_line = $block_parent->get_current_line_box();
+
+ // TODO: This is the in-flow inline position. Use the in-flow
+ // block position if the original display type is block-level
+ $inflow_y = $current_line->y - $cb["y"];
+
+ if ($height === "auto" && $top === "auto" && $bottom === "auto") {
+ // rule 3, per instruction preceding rule set
+ $top = $inflow_y;
+ $height = $content_height;
+ $bottom = $diff - $top - $height;
+ } elseif ($height === "auto" && $top === "auto") {
+ // rule 1
+ $height = $content_height;
+ $top = $diff - $height;
+ } elseif ($height === "auto" && $bottom === "auto") {
+ // rule 3
+ $height = $content_height;
+ $bottom = $diff - $height;
+ } elseif ($top === "auto" && $bottom === "auto") {
+ // rule 2
+ $top = $inflow_y;
+ $bottom = $diff - $top;
+ } elseif ($top === "auto") {
+ // rule 4
+ $top = $diff;
+ } elseif ($height === "auto") {
+ // rule 5
+ $height = max($diff, 0);
+ } else {
+ // $bottom === "auto"
+ // rule 6
+ $bottom = $diff;
+ }
+ } else {
+ // "none of the three are 'auto'" logic described in paragraph preceding the rules
+ if ($diff >= 0) {
+ if ($margin_top === "auto" && $margin_bottom === "auto") {
+ $margin_top = $margin_bottom = $diff / 2;
+ } elseif ($margin_top === "auto") {
+ $margin_top = $diff;
+ } elseif ($margin_bottom === "auto") {
+ $margin_bottom = $diff;
+ }
+ } else {
+ // over-constrained, solve for bottom
+ $bottom = $bottom + $diff;
+
+ if ($margin_top === "auto") {
+ $margin_top = 0;
+ }
+ if ($margin_bottom === "auto") {
+ $margin_bottom = 0;
+ }
+ }
+ }
+ } else {
+ // https://www.w3.org/TR/CSS21/visudet.html#normal-block
+ // https://www.w3.org/TR/CSS21/visudet.html#block-root-margin
+
+ if ($height === "auto") {
+ $height = $content_height;
+ }
+ if ($margin_top === "auto") {
+ $margin_top = 0;
+ }
+ if ($margin_bottom === "auto") {
+ $margin_bottom = 0;
+ }
+
+ // Handle min/max height
+ // https://www.w3.org/TR/CSS21/visudet.html#min-max-heights
+ $min_height = $this->resolve_min_height($cb["h"]);
+ $max_height = $this->resolve_max_height($cb["h"]);
+ $height = Helpers::clamp($height, $min_height, $max_height);
+ }
+
+ // TODO: Need to also take min/max height into account for absolute
+ // positioning, using similar logic to the `_calculate_width`/
+ // `calculate_restricted_width` split above. The non-absolute case
+ // can simply clamp height within min/max, as margins and offsets are
+ // not affected
+
+ return [$height, $margin_top, $margin_bottom, $top, $bottom];
+ }
+
+ /**
+ * Adjust the justification of each of our lines.
+ * http://www.w3.org/TR/CSS21/text.html#propdef-text-align
+ */
+ protected function _text_align()
+ {
+ $style = $this->_frame->get_style();
+ $w = $this->_frame->get_containing_block("w");
+ $width = (float)$style->length_in_pt($style->width, $w);
+ $text_indent = (float)$style->length_in_pt($style->text_indent, $w);
+
+ switch ($style->text_align) {
+ default:
+ case "left":
+ foreach ($this->_frame->get_line_boxes() as $line) {
+ if (!$line->inline) {
+ continue;
+ }
+
+ $line->trim_trailing_ws();
+
+ if ($line->left) {
+ foreach ($line->frames_to_align() as $frame) {
+ $frame->move($line->left, 0);
+ }
+ }
+ }
+ break;
+
+ case "right":
+ foreach ($this->_frame->get_line_boxes() as $i => $line) {
+ if (!$line->inline) {
+ continue;
+ }
+
+ $line->trim_trailing_ws();
+
+ $indent = $i === 0 ? $text_indent : 0;
+ $dx = $width - $line->w - $line->right - $indent;
+
+ foreach ($line->frames_to_align() as $frame) {
+ $frame->move($dx, 0);
+ }
+ }
+ break;
+
+ case "justify":
+ // We justify all lines except the last one, unless the frame
+ // has been split, in which case the actual last line is part of
+ // the split-off frame
+ $lines = $this->_frame->get_line_boxes();
+ $last_line_index = $this->_frame->is_split ? null : count($lines) - 1;
+
+ foreach ($lines as $i => $line) {
+ if (!$line->inline) {
+ continue;
+ }
+
+ $line->trim_trailing_ws();
+
+ if ($line->left) {
+ foreach ($line->frames_to_align() as $frame) {
+ $frame->move($line->left, 0);
+ }
+ }
+
+ if ($line->br || $i === $last_line_index) {
+ continue;
+ }
+
+ $frames = $line->get_frames();
+ $other_frame_count = 0;
+
+ foreach ($frames as $frame) {
+ if (!($frame instanceof TextFrameDecorator)) {
+ $other_frame_count++;
+ }
+ }
+
+ $word_count = $line->wc + $other_frame_count;
+
+ // Set the spacing for each child
+ if ($word_count > 1) {
+ $indent = $i === 0 ? $text_indent : 0;
+ $spacing = ($width - $line->get_width() - $indent) / ($word_count - 1);
+ } else {
+ $spacing = 0;
+ }
+
+ $dx = 0;
+ foreach ($frames as $frame) {
+ if ($frame instanceof TextFrameDecorator) {
+ $text = $frame->get_text();
+ $spaces = mb_substr_count($text, " ");
+
+ $frame->move($dx, 0);
+ $frame->set_text_spacing($spacing);
+
+ $dx += $spaces * $spacing;
+ } else {
+ $frame->move($dx, 0);
+ }
+ }
+
+ // The line (should) now occupy the entire width
+ $line->w = $width;
+ }
+ break;
+
+ case "center":
+ case "centre":
+ foreach ($this->_frame->get_line_boxes() as $i => $line) {
+ if (!$line->inline) {
+ continue;
+ }
+
+ $line->trim_trailing_ws();
+
+ $indent = $i === 0 ? $text_indent : 0;
+ $dx = ($width + $line->left - $line->w - $line->right - $indent) / 2;
+
+ foreach ($line->frames_to_align() as $frame) {
+ $frame->move($dx, 0);
+ }
+ }
+ break;
+ }
+ }
+
+ /**
+ * Align inline children vertically.
+ * Aligns each child vertically after each line is reflowed
+ */
+ function vertical_align()
+ {
+ $fontMetrics = $this->get_dompdf()->getFontMetrics();
+
+ foreach ($this->_frame->get_line_boxes() as $line) {
+ $height = $line->h;
+
+ // Move all markers to the top of the line box
+ foreach ($line->get_list_markers() as $marker) {
+ $x = $marker->get_position("x");
+ $marker->set_position($x, $line->y);
+ }
+
+ foreach ($line->frames_to_align() as $frame) {
+ $style = $frame->get_style();
+ $isInlineBlock = $style->display !== "inline"
+ && $style->display !== "-dompdf-list-bullet";
+
+ $baseline = $fontMetrics->getFontBaseline($style->font_family, $style->font_size);
+ $y_offset = 0;
+
+ //FIXME: The 0.8 ratio applied to the height is arbitrary (used to accommodate descenders?)
+ if ($isInlineBlock) {
+ // Workaround: Skip vertical alignment if the frame is the
+ // only one one the line, excluding empty text frames, which
+ // may be the result of trailing white space
+ // FIXME: This special case should be removed once vertical
+ // alignment is properly fixed
+ $skip = true;
+
+ foreach ($line->get_frames() as $other) {
+ if ($other !== $frame
+ && !($other->is_text_node() && $other->get_node()->nodeValue === "")
+ ) {
+ $skip = false;
+ break;
+ }
+ }
+
+ if ($skip) {
+ continue;
+ }
+
+ $marginHeight = $frame->get_margin_height();
+ $imageHeightDiff = $height * 0.8 - $marginHeight;
+
+ $align = $frame->get_style()->vertical_align;
+ if (in_array($align, Style::VERTICAL_ALIGN_KEYWORDS, true)) {
+ switch ($align) {
+ case "middle":
+ $y_offset = $imageHeightDiff / 2;
+ break;
+
+ case "sub":
+ $y_offset = 0.3 * $height + $imageHeightDiff;
+ break;
+
+ case "super":
+ $y_offset = -0.2 * $height + $imageHeightDiff;
+ break;
+
+ case "text-top": // FIXME: this should be the height of the frame minus the height of the text
+ $y_offset = $height - $style->line_height;
+ break;
+
+ case "top":
+ break;
+
+ case "text-bottom": // FIXME: align bottom of image with the descender?
+ case "bottom":
+ $y_offset = 0.3 * $height + $imageHeightDiff;
+ break;
+
+ case "baseline":
+ default:
+ $y_offset = $imageHeightDiff;
+ break;
+ }
+ } else {
+ $y_offset = $baseline - (float)$style->length_in_pt($align, $style->font_size) - $marginHeight;
+ }
+ } else {
+ $parent = $frame->get_parent();
+ if ($parent instanceof TableCellFrameDecorator) {
+ $align = "baseline";
+ } else {
+ $align = $parent->get_style()->vertical_align;
+ }
+ if (in_array($align, Style::VERTICAL_ALIGN_KEYWORDS, true)) {
+ switch ($align) {
+ case "middle":
+ $y_offset = ($height * 0.8 - $baseline) / 2;
+ break;
+
+ case "sub":
+ $y_offset = $height * 0.8 - $baseline * 0.5;
+ break;
+
+ case "super":
+ $y_offset = $height * 0.8 - $baseline * 1.4;
+ break;
+
+ case "text-top":
+ case "top": // Not strictly accurate, but good enough for now
+ break;
+
+ case "text-bottom":
+ case "bottom":
+ $y_offset = $height * 0.8 - $baseline;
+ break;
+
+ case "baseline":
+ default:
+ $y_offset = $height * 0.8 - $baseline;
+ break;
+ }
+ } else {
+ $y_offset = $height * 0.8 - $baseline - (float)$style->length_in_pt($align, $style->font_size);
+ }
+ }
+
+ if ($y_offset !== 0) {
+ $frame->move(0, $y_offset);
+ }
+ }
+ }
+ }
+
+ /**
+ * @param AbstractFrameDecorator $child
+ */
+ function process_clear(AbstractFrameDecorator $child)
+ {
+ $child_style = $child->get_style();
+ $root = $this->_frame->get_root();
+
+ // Handle "clear"
+ if ($child_style->clear !== "none") {
+ //TODO: this is a WIP for handling clear/float frames that are in between inline frames
+ if ($child->get_prev_sibling() !== null) {
+ $this->_frame->add_line();
+ }
+ if ($child_style->float !== "none" && $child->get_next_sibling()) {
+ $this->_frame->set_current_line_number($this->_frame->get_current_line_number() - 1);
+ }
+
+ $lowest_y = $root->get_lowest_float_offset($child);
+
+ // If a float is still applying, we handle it
+ if ($lowest_y) {
+ if ($child->is_in_flow()) {
+ $line_box = $this->_frame->get_current_line_box();
+ $line_box->y = $lowest_y + $child->get_margin_height();
+ $line_box->left = 0;
+ $line_box->right = 0;
+ }
+
+ $child->move(0, $lowest_y - $child->get_position("y"));
+ }
+ }
+ }
+
+ /**
+ * @param AbstractFrameDecorator $child
+ * @param float $cb_x
+ * @param float $cb_w
+ */
+ function process_float(AbstractFrameDecorator $child, $cb_x, $cb_w)
+ {
+ $child_style = $child->get_style();
+ $root = $this->_frame->get_root();
+
+ // Handle "float"
+ if ($child_style->float !== "none") {
+ $root->add_floating_frame($child);
+
+ // Remove next frame's beginning whitespace
+ $next = $child->get_next_sibling();
+ if ($next && $next instanceof TextFrameDecorator) {
+ $next->set_text(ltrim($next->get_text()));
+ }
+
+ $line_box = $this->_frame->get_current_line_box();
+ list($old_x, $old_y) = $child->get_position();
+
+ $float_x = $cb_x;
+ $float_y = $old_y;
+ $float_w = $child->get_margin_width();
+
+ if ($child_style->clear === "none") {
+ switch ($child_style->float) {
+ case "left":
+ $float_x += $line_box->left;
+ break;
+ case "right":
+ $float_x += ($cb_w - $line_box->right - $float_w);
+ break;
+ }
+ } else {
+ if ($child_style->float === "right") {
+ $float_x += ($cb_w - $float_w);
+ }
+ }
+
+ if ($cb_w < $float_x + $float_w - $old_x) {
+ // TODO handle when floating elements don't fit
+ }
+
+ $line_box->get_float_offsets();
+
+ if ($child->_float_next_line) {
+ $float_y += $line_box->h;
+ }
+
+ $child->set_position($float_x, $float_y);
+ $child->move($float_x - $old_x, $float_y - $old_y, true);
+ }
+ }
+
+ /**
+ * @param BlockFrameDecorator|null $block
+ */
+ function reflow(?BlockFrameDecorator $block = null)
+ {
+
+ // Check if a page break is forced
+ $page = $this->_frame->get_root();
+ $page->check_forced_page_break($this->_frame);
+
+ // Bail if the page is full
+ if ($page->is_full()) {
+ return;
+ }
+
+ $this->determine_absolute_containing_block();
+
+ // Counters and generated content
+ $this->_set_content();
+
+ // Inherit any dangling list markers
+ if ($block && $this->_frame->is_in_flow()) {
+ $this->_frame->inherit_dangling_markers($block);
+ }
+
+ // Collapse margins if required
+ $this->_collapse_margins();
+
+ $style = $this->_frame->get_style();
+ $cb = $this->_frame->get_containing_block();
+
+ // Determine the constraints imposed by this frame: calculate the width
+ // of the content area:
+ [$width, $margin_left, $margin_right, $left, $right] = $this->_calculate_restricted_width();
+
+ // Store the calculated properties
+ $style->set_used("width", $width);
+ $style->set_used("margin_left", $margin_left);
+ $style->set_used("margin_right", $margin_right);
+ $style->set_used("left", $left);
+ $style->set_used("right", $right);
+
+ $margin_top = $style->length_in_pt($style->margin_top, $cb["w"]);
+ $margin_bottom = $style->length_in_pt($style->margin_bottom, $cb["w"]);
+
+ $auto_top = $style->top === "auto";
+ $auto_margin_top = $margin_top === "auto";
+
+ // Update the position
+ $this->_frame->position();
+ [$x, $y] = $this->_frame->get_position();
+
+ // Adjust the first line based on the text-indent property
+ $indent = (float)$style->length_in_pt($style->text_indent, $cb["w"]);
+ $this->_frame->increase_line_width($indent);
+
+ // Determine the content edge
+ $top = (float)$style->length_in_pt([
+ $margin_top !== "auto" ? $margin_top : 0,
+ $style->border_top_width,
+ $style->padding_top
+ ], $cb["w"]);
+ $bottom = (float)$style->length_in_pt([
+ $margin_bottom !== "auto" ? $margin_bottom : 0,
+ $style->border_bottom_width,
+ $style->padding_bottom
+ ], $cb["w"]);
+
+ $cb_x = $x + (float)$margin_left + (float)$style->length_in_pt([$style->border_left_width,
+ $style->padding_left], $cb["w"]);
+
+ $cb_y = $y + $top;
+
+ $height = $style->length_in_pt($style->height, $cb["h"]);
+ if ($height === "auto") {
+ $height = ($cb["h"] + $cb["y"]) - $bottom - $cb_y;
+ }
+
+ // Set the y position of the first line in this block
+ $line_box = $this->_frame->get_current_line_box();
+ $line_box->y = $cb_y;
+ $line_box->get_float_offsets();
+
+ // Set the containing blocks and reflow each child
+ foreach ($this->_frame->get_children() as $child) {
+ $child->set_containing_block($cb_x, $cb_y, $width, $height);
+ $this->process_clear($child);
+ $child->reflow($this->_frame);
+
+ // Check for a page break before the child
+ $page->check_page_break($child);
+
+ // Don't add the child to the line if a page break has occurred
+ // before it (possibly via a descendant), in which case it has been
+ // reset, including its position
+ if ($page->is_full() && $child->get_position("x") === null) {
+ break;
+ }
+
+ $this->process_float($child, $cb_x, $width);
+ }
+
+ // Stop reflow if a page break has occurred before the frame, in which
+ // case it has been reset, including its position
+ if ($page->is_full() && $this->_frame->get_position("x") === null) {
+ return;
+ }
+
+ // Determine our height
+ [$height, $margin_top, $margin_bottom, $top, $bottom] = $this->_calculate_restricted_height();
+
+ $style->set_used("height", $height);
+ $style->set_used("margin_top", $margin_top);
+ $style->set_used("margin_bottom", $margin_bottom);
+ $style->set_used("top", $top);
+ $style->set_used("bottom", $bottom);
+
+ if ($this->_frame->is_absolute()) {
+ if ($auto_top) {
+ $this->_frame->move(0, $top);
+ }
+ if ($auto_margin_top) {
+ $this->_frame->move(0, $margin_top, true);
+ }
+ }
+
+ $this->_text_align();
+ $this->vertical_align();
+
+ // Handle relative positioning
+ foreach ($this->_frame->get_children() as $child) {
+ $this->position_relative($child);
+ }
+
+ if ($block && $this->_frame->is_in_flow()) {
+ $block->add_frame_to_line($this->_frame);
+
+ if ($this->_frame->is_block_level()) {
+ $block->add_line();
+ }
+ }
+ }
+
+ public function get_min_max_content_width(): array
+ {
+ // TODO: While the containing block is not set yet on the frame, it can
+ // already be determined in some cases due to fixed dimensions on the
+ // ancestor forming the containing block. In such cases, percentage
+ // values could be resolved here
+ $style = $this->_frame->get_style();
+ $width = $style->width;
+ $fixed_width = $width !== "auto" && !Helpers::is_percent($width);
+
+ // If the frame has a specified width, then we don't need to check
+ // its children
+ if ($fixed_width) {
+ $min = (float) $style->length_in_pt($width, 0);
+ $max = $min;
+ } else {
+ [$min, $max] = $this->get_min_max_child_width();
+ }
+
+ // Handle min/max width style properties
+ $min_width = $this->resolve_min_width(null);
+ $max_width = $this->resolve_max_width(null);
+ $min = Helpers::clamp($min, $min_width, $max_width);
+ $max = Helpers::clamp($max, $min_width, $max_width);
+
+ return [$min, $max];
+ }
+}
diff --git a/app/libs/dompdf/vendor/dompdf/dompdf/src/FrameReflower/Image.php b/app/libs/dompdf/vendor/dompdf/dompdf/src/FrameReflower/Image.php
new file mode 100644
index 0000000..42618f6
--- /dev/null
+++ b/app/libs/dompdf/vendor/dompdf/dompdf/src/FrameReflower/Image.php
@@ -0,0 +1,213 @@
+determine_absolute_containing_block();
+
+ // Counters and generated content
+ $this->_set_content();
+
+ //FLOAT
+ //$frame = $this->_frame;
+ //$page = $frame->get_root();
+
+ //if ($frame->get_style()->float !== "none" ) {
+ // $page->add_floating_frame($this);
+ //}
+
+ $this->resolve_dimensions();
+ $this->resolve_margins();
+
+ $frame = $this->_frame;
+ $frame->position();
+
+ if ($block && $frame->is_in_flow()) {
+ $block->add_frame_to_line($frame);
+ }
+ }
+
+ public function get_min_max_content_width(): array
+ {
+ // TODO: While the containing block is not set yet on the frame, it can
+ // already be determined in some cases due to fixed dimensions on the
+ // ancestor forming the containing block. In such cases, percentage
+ // values could be resolved here
+ $style = $this->_frame->get_style();
+
+ [$width] = $this->calculate_size(null, null);
+ $min_width = $this->resolve_min_width(null);
+ $percent_width = Helpers::is_percent($style->width)
+ || Helpers::is_percent($style->max_width)
+ || ($style->width === "auto"
+ && (Helpers::is_percent($style->height) || Helpers::is_percent($style->max_height)));
+
+ // Use the specified min width as minimum when width or max width depend
+ // on the containing block and cannot be resolved yet. This mimics
+ // browser behavior
+ $min = $percent_width ? $min_width : $width;
+ $max = $width;
+
+ return [$min, $max];
+ }
+
+ /**
+ * Calculate width and height, accounting for min/max constraints.
+ *
+ * * https://www.w3.org/TR/CSS21/visudet.html#inline-replaced-width
+ * * https://www.w3.org/TR/CSS21/visudet.html#inline-replaced-height
+ * * https://www.w3.org/TR/CSS21/visudet.html#min-max-widths
+ * * https://www.w3.org/TR/CSS21/visudet.html#min-max-heights
+ *
+ * @param float|null $cbw Width of the containing block.
+ * @param float|null $cbh Height of the containing block.
+ *
+ * @return float[]
+ */
+ protected function calculate_size(?float $cbw, ?float $cbh): array
+ {
+ /** @var ImageFrameDecorator */
+ $frame = $this->_frame;
+ $style = $frame->get_style();
+
+ $computed_width = $style->width;
+ $computed_height = $style->height;
+
+ $width = $cbw === null && Helpers::is_percent($computed_width)
+ ? "auto"
+ : $style->length_in_pt($computed_width, $cbw ?? 0);
+ $height = $cbh === null && Helpers::is_percent($computed_height)
+ ? "auto"
+ : $style->length_in_pt($computed_height, $cbh ?? 0);
+ $min_width = $this->resolve_min_width($cbw);
+ $max_width = $this->resolve_max_width($cbw);
+ $min_height = $this->resolve_min_height($cbh);
+ $max_height = $this->resolve_max_height($cbh);
+
+ if ($width === "auto" && $height === "auto") {
+ // Use intrinsic dimensions, resampled to pt
+ [$img_width, $img_height] = $frame->get_intrinsic_dimensions();
+ $w = $frame->resample($img_width);
+ $h = $frame->resample($img_height);
+
+ // Resolve min/max constraints according to the constraint-violation
+ // table in https://www.w3.org/TR/CSS21/visudet.html#min-max-widths
+ $max_width = max($min_width, $max_width);
+ $max_height = max($min_height, $max_height);
+
+ if (($w > $max_width && $h <= $max_height)
+ || ($w > $max_width && $h > $max_height && $max_width / $w <= $max_height / $h)
+ || ($w < $min_width && $h > $min_height)
+ || ($w < $min_width && $h < $min_height && $min_width / $w > $min_height / $h)
+ ) {
+ $width = Helpers::clamp($w, $min_width, $max_width);
+ $height = $width * ($img_height / $img_width);
+ $height = Helpers::clamp($height, $min_height, $max_height);
+ } else {
+ $height = Helpers::clamp($h, $min_height, $max_height);
+ $width = $height * ($img_width / $img_height);
+ $width = Helpers::clamp($width, $min_width, $max_width);
+ }
+ } elseif ($height === "auto") {
+ // Width is fixed, scale height according to aspect ratio
+ [$img_width, $img_height] = $frame->get_intrinsic_dimensions();
+ $width = Helpers::clamp((float) $width, $min_width, $max_width);
+ $height = $width * ($img_height / $img_width);
+ $height = Helpers::clamp($height, $min_height, $max_height);
+ } elseif ($width === "auto") {
+ // Height is fixed, scale width according to aspect ratio
+ [$img_width, $img_height] = $frame->get_intrinsic_dimensions();
+ $height = Helpers::clamp((float) $height, $min_height, $max_height);
+ $width = $height * ($img_width / $img_height);
+ $width = Helpers::clamp($width, $min_width, $max_width);
+ } else {
+ // Width and height are fixed
+ $width = Helpers::clamp((float) $width, $min_width, $max_width);
+ $height = Helpers::clamp((float) $height, $min_height, $max_height);
+ }
+
+ return [$width, $height];
+ }
+
+ protected function resolve_dimensions(): void
+ {
+ /** @var ImageFrameDecorator */
+ $frame = $this->_frame;
+ $style = $frame->get_style();
+
+ $debug_png = $this->get_dompdf()->getOptions()->getDebugPng();
+
+ if ($debug_png) {
+ [$img_width, $img_height] = $frame->get_intrinsic_dimensions();
+ print "resolve_dimensions() " .
+ $frame->get_style()->width . " " .
+ $frame->get_style()->height . ";" .
+ $frame->get_parent()->get_style()->width . " " .
+ $frame->get_parent()->get_style()->height . ";" .
+ $frame->get_parent()->get_parent()->get_style()->width . " " .
+ $frame->get_parent()->get_parent()->get_style()->height . ";" .
+ $img_width . " " .
+ $img_height . "|";
+ }
+
+ [, , $cbw, $cbh] = $frame->get_containing_block();
+ [$width, $height] = $this->calculate_size($cbw, $cbh);
+
+ if ($debug_png) {
+ print $width . " " . $height . ";";
+ }
+
+ $style->set_used("width", $width);
+ $style->set_used("height", $height);
+ }
+
+ protected function resolve_margins(): void
+ {
+ // Only handle the inline case for now
+ // https://www.w3.org/TR/CSS21/visudet.html#inline-replaced-width
+ // https://www.w3.org/TR/CSS21/visudet.html#inline-replaced-height
+ $style = $this->_frame->get_style();
+
+ if ($style->margin_left === "auto") {
+ $style->set_used("margin_left", 0.0);
+ }
+ if ($style->margin_right === "auto") {
+ $style->set_used("margin_right", 0.0);
+ }
+ if ($style->margin_top === "auto") {
+ $style->set_used("margin_top", 0.0);
+ }
+ if ($style->margin_bottom === "auto") {
+ $style->set_used("margin_bottom", 0.0);
+ }
+ }
+}
diff --git a/app/libs/dompdf/vendor/dompdf/dompdf/src/FrameReflower/Inline.php b/app/libs/dompdf/vendor/dompdf/dompdf/src/FrameReflower/Inline.php
new file mode 100644
index 0000000..d31fa45
--- /dev/null
+++ b/app/libs/dompdf/vendor/dompdf/dompdf/src/FrameReflower/Inline.php
@@ -0,0 +1,191 @@
+_frame;
+ $style = $frame->get_style();
+
+ // Resolve width, so the margin width can be checked
+ $style->set_used("width", 0.0);
+
+ $cb = $frame->get_containing_block();
+ $line = $block->get_current_line_box();
+ $width = $frame->get_margin_width();
+
+ if ($width > ($cb["w"] - $line->left - $line->w - $line->right)) {
+ $block->add_line();
+
+ // Find the appropriate inline ancestor to split
+ $child = $frame;
+ $p = $child->get_parent();
+ while ($p instanceof InlineFrameDecorator && !$child->get_prev_sibling()) {
+ $child = $p;
+ $p = $p->get_parent();
+ }
+
+ if ($p instanceof InlineFrameDecorator) {
+ // Split parent and stop current reflow. Reflow continues
+ // via child-reflow loop of split parent
+ $p->split($child);
+ return;
+ }
+ }
+
+ $frame->position();
+ $block->add_frame_to_line($frame);
+ }
+
+ /**
+ * @param BlockFrameDecorator|null $block
+ */
+ function reflow(?BlockFrameDecorator $block = null)
+ {
+ /** @var InlineFrameDecorator */
+ $frame = $this->_frame;
+
+ // Check if a page break is forced
+ $page = $frame->get_root();
+ $page->check_forced_page_break($frame);
+
+ if ($page->is_full()) {
+ return;
+ }
+
+ // Counters and generated content
+ $this->_set_content();
+
+ $style = $frame->get_style();
+
+ // Resolve auto margins
+ // https://www.w3.org/TR/CSS21/visudet.html#inline-width
+ // https://www.w3.org/TR/CSS21/visudet.html#inline-non-replaced
+ if ($style->margin_left === "auto") {
+ $style->set_used("margin_left", 0.0);
+ }
+ if ($style->margin_right === "auto") {
+ $style->set_used("margin_right", 0.0);
+ }
+ if ($style->margin_top === "auto") {
+ $style->set_used("margin_top", 0.0);
+ }
+ if ($style->margin_bottom === "auto") {
+ $style->set_used("margin_bottom", 0.0);
+ }
+
+ // Handle line breaks
+ if ($frame->get_node()->nodeName === "br") {
+ if ($block) {
+ $line = $block->get_current_line_box();
+ $frame->set_containing_line($line);
+ $block->maximize_line_height($frame->get_margin_height(), $frame);
+ $block->add_line(true);
+
+ $next = $frame->get_next_sibling();
+ $p = $frame->get_parent();
+
+ if ($next && $p instanceof InlineFrameDecorator) {
+ $p->split($next);
+ }
+ }
+ return;
+ }
+
+ // Handle empty inline frames
+ if (!$frame->get_first_child()) {
+ if ($block) {
+ $this->reflow_empty($block);
+ }
+ return;
+ }
+
+ // Add margin, padding & border width to the first and last children,
+ // so they are accounted for during text layout
+ if (($f = $frame->get_first_child()) && $f instanceof TextFrameDecorator) {
+ $f_style = $f->get_style();
+ $f_style->margin_left = $style->margin_left;
+ $f_style->padding_left = $style->padding_left;
+ $f_style->border_left_width = $style->border_left_width;
+ }
+
+ if (($l = $frame->get_last_child()) && $l instanceof TextFrameDecorator) {
+ $l_style = $l->get_style();
+ $l_style->margin_right = $style->margin_right;
+ $l_style->padding_right = $style->padding_right;
+ $l_style->border_right_width = $style->border_right_width;
+ }
+
+ $frame->position();
+
+ $cb = $frame->get_containing_block();
+
+ // Set the containing blocks and reflow each child. The containing
+ // block is not changed by line boxes.
+ foreach ($frame->get_children() as $child) {
+ $child->set_containing_block($cb);
+ $child->reflow($block);
+
+ // Stop reflow if the frame has been reset by a line or page break
+ // due to child reflow
+ if (!$frame->content_set) {
+ return;
+ }
+ }
+
+ // Assume the position of the first in-flow child, otherwise use the
+ // fallback position that was set before child reflow
+ $child = $frame->get_first_child();
+ while ($child && !$child->is_in_flow()) {
+ $child = $child->get_next_sibling();
+ }
+
+ if ($child) {
+ [$x, $y] = $child->get_position();
+ $frame->set_position($x, $y);
+ }
+
+ // Handle relative positioning
+ foreach ($frame->get_children() as $child) {
+ $this->position_relative($child);
+ }
+
+ if ($block) {
+ $block->add_frame_to_line($frame);
+ }
+ }
+}
diff --git a/app/libs/dompdf/vendor/dompdf/dompdf/src/FrameReflower/ListBullet.php b/app/libs/dompdf/vendor/dompdf/dompdf/src/FrameReflower/ListBullet.php
new file mode 100644
index 0000000..c7141ab
--- /dev/null
+++ b/app/libs/dompdf/vendor/dompdf/dompdf/src/FrameReflower/ListBullet.php
@@ -0,0 +1,51 @@
+_frame;
+ $style = $frame->get_style();
+
+ $style->set_used("width", $frame->get_width());
+ $frame->position();
+
+ if ($style->list_style_position === "inside") {
+ $block->add_frame_to_line($frame);
+ } else {
+ $block->add_dangling_marker($frame);
+ }
+ }
+}
diff --git a/app/libs/dompdf/vendor/dompdf/dompdf/src/FrameReflower/NullFrameReflower.php b/app/libs/dompdf/vendor/dompdf/dompdf/src/FrameReflower/NullFrameReflower.php
new file mode 100644
index 0000000..0159b1e
--- /dev/null
+++ b/app/libs/dompdf/vendor/dompdf/dompdf/src/FrameReflower/NullFrameReflower.php
@@ -0,0 +1,37 @@
+get_style();
+ $page_styles = $style->get_stylesheet()->get_page_styles();
+
+ // http://www.w3.org/TR/CSS21/page.html#page-selectors
+ if (count($page_styles) > 1) {
+ $odd = $page_number % 2 == 1;
+ $first = $page_number == 1;
+
+ $style = clone $page_styles["base"];
+
+ // FIXME RTL
+ if ($odd && isset($page_styles[":right"])) {
+ $style->merge($page_styles[":right"]);
+ }
+
+ if ($odd && isset($page_styles[":odd"])) {
+ $style->merge($page_styles[":odd"]);
+ }
+
+ // FIXME RTL
+ if (!$odd && isset($page_styles[":left"])) {
+ $style->merge($page_styles[":left"]);
+ }
+
+ if (!$odd && isset($page_styles[":even"])) {
+ $style->merge($page_styles[":even"]);
+ }
+
+ if ($first && isset($page_styles[":first"])) {
+ $style->merge($page_styles[":first"]);
+ }
+
+ $frame->set_style($style);
+ }
+
+ $frame->calculate_bottom_page_edge();
+ }
+
+ /**
+ * Paged layout:
+ * http://www.w3.org/TR/CSS21/page.html
+ *
+ * @param BlockFrameDecorator|null $block
+ */
+ function reflow(?BlockFrameDecorator $block = null)
+ {
+ /** @var PageFrameDecorator $frame */
+ $frame = $this->_frame;
+ $child = $frame->get_first_child();
+ $fixed_children = [];
+ $prev_child = null;
+ $current_page = 0;
+
+ // Only if it's the first page, we save the nodes with a fixed position
+ if ($child) {
+ foreach ($child->get_children() as $onechild) {
+ if ($onechild->get_style()->position === "fixed") {
+ $fixed_children[] = $onechild->deep_copy();
+ $child->remove_child($onechild);
+ }
+ }
+ $fixed_children = array_reverse($fixed_children);
+ }
+
+ while ($child) {
+ $this->apply_page_style($frame, $current_page + 1);
+
+ $style = $frame->get_style();
+
+ // Pages are only concerned with margins
+ $cb = $frame->get_containing_block();
+ $left = (float)$style->length_in_pt($style->margin_left, $cb["w"]);
+ $right = (float)$style->length_in_pt($style->margin_right, $cb["w"]);
+ $top = (float)$style->length_in_pt($style->margin_top, $cb["h"]);
+ $bottom = (float)$style->length_in_pt($style->margin_bottom, $cb["h"]);
+
+ $content_x = $cb["x"] + $left;
+ $content_y = $cb["y"] + $top;
+ $content_width = $cb["w"] - $left - $right;
+ $content_height = $cb["h"] - $top - $bottom;
+
+ $child->set_containing_block($content_x, $content_y, $content_width, $content_height);
+
+ //Insert a copy of each node which have a fixed position
+ foreach ($fixed_children as $fixed_child) {
+ $child->prepend_child($fixed_child->deep_copy());
+ }
+
+ // Check for begin reflow callback
+ $this->_check_callbacks("begin_page_reflow", $child);
+
+ $child->reflow();
+ $next_child = $child->get_next_sibling();
+
+ // Check for begin render callback
+ $this->_check_callbacks("begin_page_render", $child);
+
+ // Render the page
+ $frame->get_renderer()->render($child);
+
+ // Check for end render callback
+ $this->_check_callbacks("end_page_render", $child);
+
+ if ($next_child) {
+ $frame->next_page();
+ }
+
+ // Wait to dispose of all frames on the previous page
+ // so callback will have access to them
+ if ($prev_child) {
+ $prev_child->dispose(true);
+ }
+ $prev_child = $child;
+ $child = $next_child;
+ $current_page++;
+ }
+
+ // Dispose of previous page if it still exists
+ if ($prev_child) {
+ $prev_child->dispose(true);
+ }
+ }
+
+ /**
+ * Check for callbacks that need to be performed when a given event
+ * gets triggered on a page
+ *
+ * @param string $event The type of event
+ * @param Frame $frame The frame that event is triggered on
+ */
+ protected function _check_callbacks(string $event, Frame $frame): void
+ {
+ if (!isset($this->_callbacks)) {
+ $dompdf = $this->get_dompdf();
+ $this->_callbacks = $dompdf->getCallbacks();
+ $this->_canvas = $dompdf->getCanvas();
+ }
+
+ if (isset($this->_callbacks[$event])) {
+ $fs = $this->_callbacks[$event];
+ $canvas = $this->_canvas;
+ $fontMetrics = $this->get_dompdf()->getFontMetrics();
+
+ foreach ($fs as $f) {
+ $f($frame, $canvas, $fontMetrics);
+ }
+ }
+ }
+}
diff --git a/app/libs/dompdf/vendor/dompdf/dompdf/src/FrameReflower/Table.php b/app/libs/dompdf/vendor/dompdf/dompdf/src/FrameReflower/Table.php
new file mode 100644
index 0000000..707d18f
--- /dev/null
+++ b/app/libs/dompdf/vendor/dompdf/dompdf/src/FrameReflower/Table.php
@@ -0,0 +1,523 @@
+_state = null;
+ parent::__construct($frame);
+ }
+
+ /**
+ * State is held here so it needs to be reset along with the decorator
+ */
+ public function reset(): void
+ {
+ parent::reset();
+ $this->_state = null;
+ }
+
+ protected function _assign_widths()
+ {
+ $style = $this->_frame->get_style();
+
+ // Find the min/max width of the table and sort the columns into
+ // absolute/percent/auto arrays
+ $delta = $this->_state["width_delta"];
+ $min_width = $this->_state["min_width"];
+ $max_width = $this->_state["max_width"];
+ $percent_used = $this->_state["percent_used"];
+ $absolute_used = $this->_state["absolute_used"];
+ $auto_min = $this->_state["auto_min"];
+
+ $absolute =& $this->_state["absolute"];
+ $percent =& $this->_state["percent"];
+ $auto =& $this->_state["auto"];
+
+ // Determine the actual width of the table (excluding borders and
+ // padding)
+ $cb = $this->_frame->get_containing_block();
+ $columns =& $this->_frame->get_cellmap()->get_columns();
+
+ $width = $style->width;
+ $min_table_width = $this->resolve_min_width($cb["w"]) - $delta;
+
+ if ($width !== "auto") {
+ $preferred_width = (float) $style->length_in_pt($width, $cb["w"]) - $delta;
+
+ if ($preferred_width < $min_table_width) {
+ $preferred_width = $min_table_width;
+ }
+
+ if ($preferred_width > $min_width) {
+ $width = $preferred_width;
+ } else {
+ $width = $min_width;
+ }
+
+ } else {
+ if ($max_width + $delta < $cb["w"]) {
+ $width = $max_width;
+ } elseif ($cb["w"] - $delta > $min_width) {
+ $width = $cb["w"] - $delta;
+ } else {
+ $width = $min_width;
+ }
+
+ if ($width < $min_table_width) {
+ $width = $min_table_width;
+ }
+
+ }
+
+ // Store our resolved width
+ $style->set_used("width", $width);
+
+ $cellmap = $this->_frame->get_cellmap();
+
+ if ($cellmap->is_columns_locked()) {
+ return;
+ }
+
+ // If the whole table fits on the page, then assign each column it's max width
+ if ($width == $max_width) {
+ foreach ($columns as $i => $col) {
+ $cellmap->set_column_width($i, $col["max-width"]);
+ }
+
+ return;
+ }
+
+ // Determine leftover and assign it evenly to all columns
+ if ($width > $min_width) {
+ // We have three cases to deal with:
+ //
+ // 1. All columns are auto or absolute width. In this case we
+ // distribute extra space across all auto columns weighted by the
+ // difference between their max and min width, or by max width only
+ // if the width of the table is larger than the max width for all
+ // columns.
+ //
+ // 2. Only absolute widths have been specified, no auto columns. In
+ // this case we distribute extra space across all columns weighted
+ // by their absolute width.
+ //
+ // 3. Percentage widths have been specified. In this case we normalize
+ // the percentage values and try to assign widths as fractions of
+ // the table width. Absolute column widths are fully satisfied and
+ // any remaining space is evenly distributed among all auto columns.
+
+ // Case 1:
+ if ($percent_used == 0 && count($auto)) {
+ foreach ($absolute as $i) {
+ $w = $columns[$i]["min-width"];
+ $cellmap->set_column_width($i, $w);
+ }
+
+ if ($width < $max_width) {
+ $increment = $width - $min_width;
+ $table_delta = $max_width - $min_width;
+
+ foreach ($auto as $i) {
+ $min = $columns[$i]["min-width"];
+ $max = $columns[$i]["max-width"];
+ $col_delta = $max - $min;
+ $w = $min + $increment * ($col_delta / $table_delta);
+ $cellmap->set_column_width($i, $w);
+ }
+ } else {
+ $increment = $width - $max_width;
+ $auto_max = $max_width - $absolute_used;
+
+ foreach ($auto as $i) {
+ $max = $columns[$i]["max-width"];
+ $f = $auto_max > 0 ? $max / $auto_max : 1 / count($auto);
+ $w = $max + $increment * $f;
+ $cellmap->set_column_width($i, $w);
+ }
+ }
+ return;
+ }
+
+ // Case 2:
+ if ($percent_used == 0 && !count($auto)) {
+ $increment = $width - $absolute_used;
+
+ foreach ($absolute as $i) {
+ $abs = $columns[$i]["min-width"];
+ $f = $absolute_used > 0 ? $abs / $absolute_used : 1 / count($absolute);
+ $w = $abs + $increment * $f;
+ $cellmap->set_column_width($i, $w);
+ }
+ return;
+ }
+
+ // Case 3:
+ if ($percent_used > 0) {
+ // Scale percent values if the total percentage is > 100 or
+ // there are no auto values to take up slack
+ if ($percent_used > 100 || count($auto) == 0) {
+ $scale = 100 / $percent_used;
+ } else {
+ $scale = 1;
+ }
+
+ // Account for the minimum space used by the unassigned auto
+ // columns, by the columns with absolute widths, and the
+ // percentage columns following the current one
+ $used_width = $auto_min + $absolute_used;
+
+ foreach ($absolute as $i) {
+ $w = $columns[$i]["min-width"];
+ $cellmap->set_column_width($i, $w);
+ }
+
+ $percent_min = 0;
+
+ foreach ($percent as $i) {
+ $percent_min += $columns[$i]["min-width"];
+ }
+
+ // First-come, first served
+ foreach ($percent as $i) {
+ $min = $columns[$i]["min-width"];
+ $percent_min -= $min;
+ $slack = $width - $used_width - $percent_min;
+
+ $columns[$i]["percent"] *= $scale;
+ $w = min($columns[$i]["percent"] * $width / 100, $slack);
+
+ if ($w < $min) {
+ $w = $min;
+ }
+
+ $cellmap->set_column_width($i, $w);
+ $used_width += $w;
+ }
+
+ // This works because $used_width includes the min-width of each
+ // unassigned column
+ if (count($auto) > 0) {
+ $increment = ($width - $used_width) / count($auto);
+
+ foreach ($auto as $i) {
+ $w = $columns[$i]["min-width"] + $increment;
+ $cellmap->set_column_width($i, $w);
+ }
+ }
+ return;
+ }
+ } else {
+ // We are over-constrained:
+ // Each column gets its minimum width
+ foreach ($columns as $i => $col) {
+ $cellmap->set_column_width($i, $col["min-width"]);
+ }
+ }
+ }
+
+ /**
+ * Determine the frame's height based on min/max height
+ *
+ * @return float
+ */
+ protected function _calculate_height()
+ {
+ $frame = $this->_frame;
+ $style = $frame->get_style();
+ $cb = $frame->get_containing_block();
+
+ $height = $style->length_in_pt($style->height, $cb["h"]);
+
+ $cellmap = $frame->get_cellmap();
+ $cellmap->assign_frame_heights();
+ $rows = $cellmap->get_rows();
+
+ // Determine our content height
+ $content_height = 0.0;
+ foreach ($rows as $r) {
+ $content_height += $r["height"];
+ }
+
+ if ($height === "auto") {
+ $height = $content_height;
+ }
+
+ // Handle min/max height
+ // https://www.w3.org/TR/CSS21/visudet.html#min-max-heights
+ $min_height = $this->resolve_min_height($cb["h"]);
+ $max_height = $this->resolve_max_height($cb["h"]);
+ $height = Helpers::clamp($height, $min_height, $max_height);
+
+ // Use the content height or the height value, whichever is greater
+ if ($height <= $content_height) {
+ $height = $content_height;
+ } else {
+ // FIXME: Borders and row positions are not properly updated by this
+ // $cellmap->set_frame_heights($height, $content_height);
+ }
+
+ return $height;
+ }
+
+ /**
+ * @param BlockFrameDecorator|null $block
+ */
+ function reflow(?BlockFrameDecorator $block = null)
+ {
+ /** @var TableFrameDecorator */
+ $frame = $this->_frame;
+
+ // Check if a page break is forced
+ $page = $frame->get_root();
+ $page->check_forced_page_break($frame);
+
+ // Bail if the page is full
+ if ($page->is_full()) {
+ return;
+ }
+
+ // Let the page know that we're reflowing a table so that splits
+ // are suppressed (simply setting page-break-inside: avoid won't
+ // work because we may have an arbitrary number of block elements
+ // inside tds.)
+ $page->table_reflow_start();
+
+ $this->determine_absolute_containing_block();
+
+ // Counters and generated content
+ $this->_set_content();
+
+ // Collapse vertical margins, if required
+ $this->_collapse_margins();
+
+ // Table layout algorithm:
+ // http://www.w3.org/TR/CSS21/tables.html#auto-table-layout
+
+ if (is_null($this->_state)) {
+ $this->get_min_max_width();
+ }
+
+ $cb = $frame->get_containing_block();
+ $style = $frame->get_style();
+
+ // This is slightly inexact, but should be okay. Add half the
+ // border-spacing to the table as padding. The other half is added to
+ // the cells themselves.
+ if ($style->border_collapse === "separate") {
+ [$h, $v] = $style->border_spacing;
+ $v = $v / 2;
+ $h = $h / 2;
+
+ $style->set_used("padding_left", (float)$style->length_in_pt($style->padding_left, $cb["w"]) + $h);
+ $style->set_used("padding_right", (float)$style->length_in_pt($style->padding_right, $cb["w"]) + $h);
+ $style->set_used("padding_top", (float)$style->length_in_pt($style->padding_top, $cb["w"]) + $v);
+ $style->set_used("padding_bottom", (float)$style->length_in_pt($style->padding_bottom, $cb["w"]) + $v);
+ }
+
+ $this->_assign_widths();
+
+ // Adjust left & right margins, if they are auto
+ $delta = $this->_state["width_delta"];
+ $width = $style->width;
+ $left = $style->length_in_pt($style->margin_left, $cb["w"]);
+ $right = $style->length_in_pt($style->margin_right, $cb["w"]);
+
+ $diff = (float) $cb["w"] - (float) $width - $delta;
+
+ if ($left === "auto" && $right === "auto") {
+ if ($diff < 0) {
+ $left = 0;
+ $right = $diff;
+ } else {
+ $left = $right = $diff / 2;
+ }
+ } else {
+ if ($left === "auto") {
+ $left = max($diff - $right, 0);
+ }
+ if ($right === "auto") {
+ $right = max($diff - $left, 0);
+ }
+ }
+
+ $style->set_used("margin_left", $left);
+ $style->set_used("margin_right", $right);
+
+ $frame->position();
+ [$x, $y] = $frame->get_position();
+
+ // Determine the content edge
+ $offset_x = (float)$left + (float)$style->length_in_pt([
+ $style->padding_left,
+ $style->border_left_width
+ ], $cb["w"]);
+ $offset_y = (float)$style->length_in_pt([
+ $style->margin_top,
+ $style->border_top_width,
+ $style->padding_top
+ ], $cb["w"]);
+ $content_x = $x + $offset_x;
+ $content_y = $y + $offset_y;
+
+ if (isset($cb["h"])) {
+ $h = $cb["h"];
+ } else {
+ $h = null;
+ }
+
+ $cellmap = $frame->get_cellmap();
+ $col =& $cellmap->get_column(0);
+ $col["x"] = $offset_x;
+
+ $row =& $cellmap->get_row(0);
+ $row["y"] = $offset_y;
+
+ $cellmap->assign_x_positions();
+
+ // Set the containing block of each child & reflow
+ foreach ($frame->get_children() as $child) {
+ $child->set_containing_block($content_x, $content_y, $width, $h);
+ $child->reflow();
+
+ if (!$page->in_nested_table()) {
+ // Check if a split has occurred
+ $page->check_page_break($child);
+
+ if ($page->is_full()) {
+ break;
+ }
+ }
+ }
+
+ // Stop reflow if a page break has occurred before the frame, in which
+ // case it has been reset, including its position
+ if ($page->is_full() && $frame->get_position("x") === null) {
+ $page->table_reflow_end();
+ return;
+ }
+
+ // Assign heights to our cells:
+ $style->set_used("height", $this->_calculate_height());
+
+ $page->table_reflow_end();
+
+ if ($block && $frame->is_in_flow()) {
+ $block->add_frame_to_line($frame);
+
+ if ($frame->is_block_level()) {
+ $block->add_line();
+ }
+ }
+ }
+
+ public function get_min_max_width(): array
+ {
+ if (!is_null($this->_min_max_cache)) {
+ return $this->_min_max_cache;
+ }
+
+ $style = $this->_frame->get_style();
+ $cellmap = $this->_frame->get_cellmap();
+
+ $this->_frame->normalize();
+
+ // Add the cells to the cellmap (this will calculate column widths as
+ // frames are added)
+ $cellmap->add_frame($this->_frame);
+
+ // Find the min/max width of the table and sort the columns into
+ // absolute/percent/auto arrays
+ $this->_state = [];
+ $this->_state["min_width"] = 0;
+ $this->_state["max_width"] = 0;
+
+ $this->_state["percent_used"] = 0;
+ $this->_state["absolute_used"] = 0;
+ $this->_state["auto_min"] = 0;
+
+ $this->_state["absolute"] = [];
+ $this->_state["percent"] = [];
+ $this->_state["auto"] = [];
+
+ $columns =& $cellmap->get_columns();
+ foreach ($columns as $i => $col) {
+ $this->_state["min_width"] += $col["min-width"];
+ $this->_state["max_width"] += $col["max-width"];
+
+ if ($col["absolute"] > 0) {
+ $this->_state["absolute"][] = $i;
+ $this->_state["absolute_used"] += $col["min-width"];
+ } elseif ($col["percent"] > 0) {
+ $this->_state["percent"][] = $i;
+ $this->_state["percent_used"] += $col["percent"];
+ } else {
+ $this->_state["auto"][] = $i;
+ $this->_state["auto_min"] += $col["min-width"];
+ }
+ }
+
+ // Account for margins, borders, padding, and border spacing
+ $cb_w = $this->_frame->get_containing_block("w");
+ $lm = (float) $style->length_in_pt($style->margin_left, $cb_w);
+ $rm = (float) $style->length_in_pt($style->margin_right, $cb_w);
+
+ $dims = [
+ $style->border_left_width,
+ $style->border_right_width,
+ $style->padding_left,
+ $style->padding_right
+ ];
+
+ if ($style->border_collapse !== "collapse") {
+ list($dims[]) = $style->border_spacing;
+ }
+
+ $delta = (float) $style->length_in_pt($dims, $cb_w);
+
+ $this->_state["width_delta"] = $delta;
+
+ $min_width = $this->_state["min_width"] + $delta + $lm + $rm;
+ $max_width = $this->_state["max_width"] + $delta + $lm + $rm;
+
+ return $this->_min_max_cache = [
+ $min_width,
+ $max_width,
+ "min" => $min_width,
+ "max" => $max_width
+ ];
+ }
+}
diff --git a/app/libs/dompdf/vendor/dompdf/dompdf/src/FrameReflower/TableCell.php b/app/libs/dompdf/vendor/dompdf/dompdf/src/FrameReflower/TableCell.php
new file mode 100644
index 0000000..e63029f
--- /dev/null
+++ b/app/libs/dompdf/vendor/dompdf/dompdf/src/FrameReflower/TableCell.php
@@ -0,0 +1,165 @@
+_frame;
+ $table = TableFrameDecorator::find_parent_table($frame);
+ if ($table === null) {
+ throw new Exception("Parent table not found for table cell");
+ }
+
+ // Counters and generated content
+ $this->_set_content();
+
+ $style = $frame->get_style();
+ $cellmap = $table->get_cellmap();
+
+ [$x, $y] = $cellmap->get_frame_position($frame);
+ $frame->set_position($x, $y);
+
+ $cells = $cellmap->get_spanned_cells($frame);
+
+ $w = 0;
+ foreach ($cells["columns"] as $i) {
+ $col = $cellmap->get_column($i);
+ $w += $col["used-width"];
+ }
+
+ //FIXME?
+ $h = $frame->get_containing_block("h");
+
+ $left_space = (float)$style->length_in_pt([$style->margin_left,
+ $style->padding_left,
+ $style->border_left_width],
+ $w);
+
+ $right_space = (float)$style->length_in_pt([$style->padding_right,
+ $style->margin_right,
+ $style->border_right_width],
+ $w);
+
+ $top_space = (float)$style->length_in_pt([$style->margin_top,
+ $style->padding_top,
+ $style->border_top_width],
+ $h);
+ $bottom_space = (float)$style->length_in_pt([$style->margin_bottom,
+ $style->padding_bottom,
+ $style->border_bottom_width],
+ $h);
+
+ $cb_w = $w - $left_space - $right_space;
+ $style->set_used("width", $cb_w);
+
+ $content_x = $x + $left_space;
+ $content_y = $line_y = $y + $top_space;
+
+ // Adjust the first line based on the text-indent property
+ $indent = (float)$style->length_in_pt($style->text_indent, $w);
+ $frame->increase_line_width($indent);
+
+ $page = $frame->get_root();
+
+ // Set the y position of the first line in the cell
+ $line_box = $frame->get_current_line_box();
+ $line_box->y = $line_y;
+
+ // Set the containing blocks and reflow each child
+ foreach ($frame->get_children() as $child) {
+ $child->set_containing_block($content_x, $content_y, $cb_w, $h);
+ $this->process_clear($child);
+ $child->reflow($frame);
+ $this->process_float($child, $content_x, $cb_w);
+
+ if ($page->is_full()) {
+ break;
+ }
+ }
+
+ // Determine our height
+ $style_height = (float) $style->length_in_pt($style->height, $h);
+ $content_height = $this->_calculate_content_height();
+ $height = max($style_height, $content_height);
+
+ $frame->set_content_height($content_height);
+
+ // Let the cellmap know our height
+ $cell_height = $height / count($cells["rows"]);
+
+ if ($style_height <= $height) {
+ $cell_height += $top_space + $bottom_space;
+ }
+
+ foreach ($cells["rows"] as $i) {
+ $cellmap->set_row_height($i, $cell_height);
+ }
+
+ $style->set_used("height", $height);
+
+ $this->_text_align();
+ $this->vertical_align();
+
+ // Handle relative positioning
+ foreach ($frame->get_children() as $child) {
+ $this->position_relative($child);
+ }
+ }
+
+ public function get_min_max_content_width(): array
+ {
+ // Ignore percentage values for a specified width here, as they are
+ // relative to the table width, which is not determined yet
+ $style = $this->_frame->get_style();
+ $width = $style->width;
+ $fixed_width = $width !== "auto" && !Helpers::is_percent($width);
+
+ [$min, $max] = $this->get_min_max_child_width();
+
+ // For table cells: Use specified width if it is greater than the
+ // minimum defined by the content
+ if ($fixed_width) {
+ $width = (float) $style->length_in_pt($width, 0);
+ $min = max($width, $min);
+ $max = $min;
+ }
+
+ // Handle min/max width style properties
+ $min_width = $this->resolve_min_width(null);
+ $max_width = $this->resolve_max_width(null);
+ $min = Helpers::clamp($min, $min_width, $max_width);
+ $max = Helpers::clamp($max, $min_width, $max_width);
+
+ return [$min, $max];
+ }
+}
diff --git a/app/libs/dompdf/vendor/dompdf/dompdf/src/FrameReflower/TableRow.php b/app/libs/dompdf/vendor/dompdf/dompdf/src/FrameReflower/TableRow.php
new file mode 100644
index 0000000..5115a24
--- /dev/null
+++ b/app/libs/dompdf/vendor/dompdf/dompdf/src/FrameReflower/TableRow.php
@@ -0,0 +1,86 @@
+_frame;
+
+ // Check if a page break is forced
+ $page = $frame->get_root();
+ $page->check_forced_page_break($frame);
+
+ // Bail if the page is full
+ if ($page->is_full()) {
+ return;
+ }
+
+ // Counters and generated content
+ $this->_set_content();
+
+ $frame->position();
+ $style = $frame->get_style();
+ $cb = $frame->get_containing_block();
+
+ foreach ($frame->get_children() as $child) {
+ $child->set_containing_block($cb);
+ $child->reflow();
+
+ if ($page->is_full()) {
+ break;
+ }
+ }
+
+ if ($page->is_full()) {
+ return;
+ }
+
+ $table = TableFrameDecorator::find_parent_table($frame);
+ if ($table === null) {
+ throw new Exception("Parent table not found for table row");
+ }
+ $cellmap = $table->get_cellmap();
+
+ $style->set_used("width", $cellmap->get_frame_width($frame));
+ $style->set_used("height", $cellmap->get_frame_height($frame));
+
+ $frame->set_position($cellmap->get_frame_position($frame));
+ }
+
+ /**
+ * @throws Exception
+ */
+ public function get_min_max_width(): array
+ {
+ throw new Exception("Min/max width is undefined for table rows");
+ }
+}
diff --git a/app/libs/dompdf/vendor/dompdf/dompdf/src/FrameReflower/TableRowGroup.php b/app/libs/dompdf/vendor/dompdf/dompdf/src/FrameReflower/TableRowGroup.php
new file mode 100644
index 0000000..20f5ee6
--- /dev/null
+++ b/app/libs/dompdf/vendor/dompdf/dompdf/src/FrameReflower/TableRowGroup.php
@@ -0,0 +1,81 @@
+_frame;
+ $page = $frame->get_root();
+ $parent = $frame->get_parent();
+ $dompdf_generated = $parent->get_frame()->get_node()->nodeName === "dompdf_generated";
+
+ // Counters and generated content
+ $this->_set_content();
+
+ $style = $frame->get_style();
+ $cb = $frame->get_containing_block();
+
+ foreach ($frame->get_children() as $child) {
+ $child->set_containing_block($cb["x"], $cb["y"], $cb["w"], $cb["h"]);
+ $child->reflow();
+
+ // Check if a split has occurred
+ $page->check_page_break($child);
+
+ if ($page->is_full()) {
+ break;
+ }
+ }
+
+ if ($page->is_full() && $dompdf_generated && $frame->get_parent() === null) {
+ return;
+ }
+
+ $table = TableFrameDecorator::find_parent_table($frame);
+ if ($table === null) {
+ throw new Exception("Parent table not found for table row group");
+ }
+ $cellmap = $table->get_cellmap();
+
+ // Stop reflow if a page break has occurred before the frame, in which
+ // case it is not part of its parent table's cell map yet
+ if ($page->is_full() && !$cellmap->frame_exists_in_cellmap($frame)) {
+ return;
+ }
+
+ $style->set_used("width", $cellmap->get_frame_width($frame));
+ $style->set_used("height", $cellmap->get_frame_height($frame));
+
+ $frame->set_position($cellmap->get_frame_position($frame));
+ }
+}
diff --git a/app/libs/dompdf/vendor/dompdf/dompdf/src/FrameReflower/Text.php b/app/libs/dompdf/vendor/dompdf/dompdf/src/FrameReflower/Text.php
new file mode 100644
index 0000000..7564910
--- /dev/null
+++ b/app/libs/dompdf/vendor/dompdf/dompdf/src/FrameReflower/Text.php
@@ -0,0 +1,601 @@
+
+ */
+ const SOFT_HYPHEN = "\xC2\xAD";
+
+ /**
+ * The regex splits on everything that's a separator (^\S double negative),
+ * excluding the following non-breaking space characters:
+ * * nbsp (\xA0)
+ * * narrow nbsp (\x{202F})
+ * * figure space (\x{2007})
+ */
+ public static $_whitespace_pattern = '/([^\S\xA0\x{202F}\x{2007}]+)/u';
+
+ /**
+ * The regex splits on everything that's a separator (^\S double negative)
+ * plus dashes, excluding the following non-breaking space characters:
+ * * nbsp (\xA0)
+ * * narrow nbsp (\x{202F})
+ * * figure space (\x{2007})
+ */
+ public static $_wordbreak_pattern = '/([^\S\xA0\x{202F}\x{2007}\n]+|\R|\-+|\xAD+)/u';
+
+ /**
+ * Frame for this reflower
+ *
+ * @var TextFrameDecorator
+ */
+ protected $_frame;
+
+ /**
+ * @var FontMetrics
+ */
+ private $fontMetrics;
+
+ /**
+ * @param TextFrameDecorator $frame
+ * @param FontMetrics $fontMetrics
+ */
+ public function __construct(TextFrameDecorator $frame, FontMetrics $fontMetrics)
+ {
+ parent::__construct($frame);
+ $this->setFontMetrics($fontMetrics);
+ }
+
+ /**
+ * Apply text transform and white-space collapse according to style.
+ *
+ * * http://www.w3.org/TR/CSS21/text.html#propdef-text-transform
+ * * http://www.w3.org/TR/CSS21/text.html#propdef-white-space
+ *
+ * @param string $text
+ * @return string
+ */
+ protected function pre_process_text(string $text): string
+ {
+ $style = $this->_frame->get_style();
+
+ // Handle text transform
+ switch ($style->text_transform) {
+ case "capitalize":
+ $text = Helpers::mb_ucwords($text);
+ break;
+ case "uppercase":
+ $text = mb_convert_case($text, MB_CASE_UPPER, "UTF-8");
+ break;
+ case "lowercase":
+ $text = mb_convert_case($text, MB_CASE_LOWER, "UTF-8");
+ break;
+ default:
+ break;
+ }
+
+ // Handle white-space collapse
+ switch ($style->white_space) {
+ default:
+ case "normal":
+ case "nowrap":
+ $text = preg_replace(self::$_whitespace_pattern, " ", $text) ?? "";
+ break;
+
+ case "pre-line":
+ // Collapse white space except for line breaks
+ $text = preg_replace('/([^\S\xA0\x{202F}\x{2007}\n]+)/u', " ", $text) ?? "";
+ break;
+
+ case "pre":
+ case "pre-wrap":
+ break;
+
+ }
+
+ return $text;
+ }
+
+ /**
+ * @param string $text
+ * @param BlockFrameDecorator $block
+ * @param bool $nowrap
+ *
+ * @return int|false
+ */
+ protected function line_break(string $text, BlockFrameDecorator $block, bool $nowrap = false)
+ {
+ $fontMetrics = $this->getFontMetrics();
+ $frame = $this->_frame;
+ $style = $frame->get_style();
+ $font = $style->font_family;
+ $size = $style->font_size;
+ $word_spacing = $style->word_spacing;
+ $letter_spacing = $style->letter_spacing;
+
+ // Determine the available width
+ $current_line = $block->get_current_line_box();
+ $line_width = $frame->get_containing_block("w");
+ $current_line_width = $current_line->left + $current_line->w + $current_line->right;
+ $available_width = $line_width - $current_line_width;
+
+ // Determine the frame width including margin, padding & border
+ $visible_text = preg_replace('/\xAD/u', "", $text);
+ $text_width = $fontMetrics->getTextWidth($visible_text, $font, $size, $word_spacing, $letter_spacing);
+ $mbp_width = (float) $style->length_in_pt([
+ $style->margin_left,
+ $style->border_left_width,
+ $style->padding_left,
+ $style->padding_right,
+ $style->border_right_width,
+ $style->margin_right
+ ], $line_width);
+ $frame_width = $text_width + $mbp_width;
+
+ if (Helpers::lengthLessOrEqual($frame_width, $available_width)) {
+ return false;
+ }
+
+ $force_first = $current_line->left == 0
+ && $current_line->right == 0
+ && $current_line->is_empty();
+
+ if ($nowrap) {
+ return $force_first ? false : 0;
+ }
+
+ // Split the text into words
+ $words = preg_split(self::$_wordbreak_pattern, $text, -1, PREG_SPLIT_DELIM_CAPTURE);
+ $wc = count($words);
+
+ // Determine the split point
+ $width = 0.0;
+ $str = "";
+
+ $space_width = $fontMetrics->getTextWidth(" ", $font, $size, $word_spacing, $letter_spacing);
+ $shy_width = $fontMetrics->getTextWidth(self::SOFT_HYPHEN, $font, $size);
+
+ // @todo support
+ for ($i = 0; $i < $wc; $i += 2) {
+ // Allow trailing white space to overflow. White space is always
+ // collapsed to the standard space character currently, so only
+ // handle that for now
+ $sep = $words[$i + 1] ?? "";
+ $word = $sep === " " ? $words[$i] : $words[$i] . $sep;
+ $word_width = $fontMetrics->getTextWidth($word, $font, $size, $word_spacing, $letter_spacing);
+ $used_width = $width + $word_width + $mbp_width;
+
+ if ($used_width > 0 && Helpers::lengthGreater($used_width, $available_width)) {
+ // If the previous split happened by soft hyphen, we have to
+ // append its width again because the last hyphen of a line
+ // won't be removed
+ if (isset($words[$i - 1]) && self::SOFT_HYPHEN === $words[$i - 1]) {
+ $width += $shy_width;
+ }
+ break;
+ }
+
+ // If the word is splitted by soft hyphen, but no line break is needed
+ // we have to reduce the width. But the str is not modified, otherwise
+ // the wrong offset is calculated at the end of this method.
+ if ($sep === self::SOFT_HYPHEN) {
+ $width += $word_width - $shy_width;
+ $str .= $word;
+ } elseif ($sep === " ") {
+ $width += $word_width + $space_width;
+ $str .= $word . $sep;
+ } else {
+ $width += $word_width;
+ $str .= $word;
+ }
+ }
+
+ // The first word has overflowed. Force it onto the line, or as many
+ // characters as fit if breaking words is allowed
+ if ($force_first && $width === 0.0) {
+ if ($sep === " ") {
+ $word .= $sep;
+ }
+
+ // https://www.w3.org/TR/css-text-3/#overflow-wrap-property
+ $wrap = $style->overflow_wrap;
+ $break_word = $wrap === "anywhere" || $wrap === "break-word";
+
+ if ($break_word) {
+ $s = "";
+ $len = mb_strlen($word, "UTF-8");
+
+ for ($j = 0; $j < $len; $j++) {
+ $c = mb_substr($word, $j, 1, "UTF-8");
+ $w = $fontMetrics->getTextWidth($s . $c, $font, $size, $word_spacing, $letter_spacing);
+
+ if (Helpers::lengthGreater($w, $available_width)) {
+ break;
+ }
+
+ $s .= $c;
+ }
+
+ // Always force the first character onto the line
+ $str = $j === 0 ? $s . $c : $s;
+ } else {
+ $str = $word;
+ }
+ }
+
+ $offset = mb_strlen($str, "UTF-8");
+ return $offset;
+ }
+
+ /**
+ * @param string $text
+ * @return int|false
+ */
+ protected function newline_break(string $text)
+ {
+ if (($i = mb_strpos($text, "\n", 0, "UTF-8")) === false) {
+ return false;
+ }
+
+ return $i + 1;
+ }
+
+ /**
+ * @param BlockFrameDecorator $block
+ * @return bool|null Whether to add a new line at the end. `null` if reflow
+ * should be stopped.
+ */
+ protected function layout_line(BlockFrameDecorator $block): ?bool
+ {
+ $frame = $this->_frame;
+ $style = $frame->get_style();
+ $current_line = $block->get_current_line_box();
+ $text = $frame->get_text();
+
+ // Trim leading white space if this is the first text on the line
+ if ($current_line->is_empty() && !$frame->is_pre()) {
+ $text = ltrim($text, " ");
+ }
+
+ if ($text === "") {
+ $frame->set_text("");
+ $style->set_used("width", 0.0);
+ return false;
+ }
+
+ // Determine the next line break
+ // http://www.w3.org/TR/CSS21/text.html#propdef-white-space
+ $white_space = $style->white_space;
+ $nowrap = $white_space === "nowrap" || $white_space === "pre";
+
+ switch ($white_space) {
+ default:
+ case "normal":
+ case "nowrap":
+ $split = $this->line_break($text, $block, $nowrap);
+ $add_line = false;
+ break;
+
+ case "pre":
+ case "pre-line":
+ case "pre-wrap":
+ $hard_split = $this->newline_break($text);
+ $first_line = $hard_split !== false
+ ? mb_substr($text, 0, $hard_split, "UTF-8")
+ : $text;
+ $soft_split = $this->line_break($first_line, $block, $nowrap);
+
+ $split = $soft_split !== false ? $soft_split : $hard_split;
+ $add_line = $hard_split !== false;
+ break;
+ }
+
+ if ($split === 0) {
+ // Make sure to move text when floating frames leave no space to
+ // place anything onto the line
+ // TODO: Would probably be better to move just below the current
+ // floating frame instead of trying to place text in line-height
+ // increments
+ if ($current_line->h === 0.0) {
+ // Line height might be 0
+ $h = max($frame->get_margin_height(), 1.0);
+ $block->maximize_line_height($h, $frame);
+ }
+
+ // Break line and repeat layout
+ $block->add_line();
+
+ // Find the appropriate inline ancestor to split
+ $child = $frame;
+ $p = $child->get_parent();
+ while ($p instanceof InlineFrameDecorator && !$child->get_prev_sibling()) {
+ $child = $p;
+ $p = $p->get_parent();
+ }
+
+ if ($p instanceof InlineFrameDecorator) {
+ // Split parent and stop current reflow. Reflow continues
+ // via child-reflow loop of split parent
+ $p->split($child);
+ return null;
+ }
+
+ return $this->layout_line($block);
+ }
+
+ // Final split point is determined
+ if ($split !== false && $split < mb_strlen($text, "UTF-8")) {
+ // Split the line
+ $frame->set_text($text);
+ $frame->split_text($split, true);
+ $add_line = true;
+
+ // Remove inner soft hyphens
+ $t = $frame->get_text();
+ $shyPosition = mb_strpos($t, self::SOFT_HYPHEN, 0, "UTF-8");
+ if (false !== $shyPosition && $shyPosition < mb_strlen($t, "UTF-8") - 1) {
+ $t = str_replace(self::SOFT_HYPHEN, "", mb_substr($t, 0, -1, "UTF-8")) . mb_substr($t, -1, null, "UTF-8");
+ $frame->set_text($t);
+ }
+ } else {
+ // No split required
+ // Remove soft hyphens
+ $text = str_replace(self::SOFT_HYPHEN, "", $text);
+ $frame->set_text($text);
+ }
+
+ // Set our new width
+ $frame->recalculate_width();
+
+ return $add_line;
+ }
+
+ /**
+ * @param BlockFrameDecorator|null $block
+ * @throws Exception
+ */
+ function reflow(?BlockFrameDecorator $block = null)
+ {
+ $frame = $this->_frame;
+ $page = $frame->get_root();
+ $page->check_forced_page_break($frame);
+
+ if ($page->is_full()) {
+ return;
+ }
+
+ $style = $frame->get_style();
+
+ // Handle text transform and white space
+ $frame->set_text($this->pre_process_text($frame->get_text()));
+
+ // map text to fonts based on supported Unicode range
+ $frame->apply_font_mapping();
+ $text = $frame->get_text();
+
+ // Determine the text height
+ $size = $style->font_size;
+ $font = $style->font_family;
+ $font_height = $this->getFontMetrics()->getFontHeight($font, $size);
+ $style->set_used("height", $font_height);
+
+ if ($block === null) {
+ return;
+ }
+
+ $add_line = $this->layout_line($block);
+
+ if ($add_line === null) {
+ return;
+ }
+
+ $frame->position();
+
+ // Skip wrapped white space between block-level elements in case white
+ // space is collapsed
+ $text = $frame->get_text();
+ if ($text === "" && $frame->get_margin_width() === 0.0) {
+ return;
+ }
+
+ $line = $block->add_frame_to_line($frame);
+ $trimmed = trim($text);
+
+ // Split the text into words (used to determine spacing between
+ // words on justified lines)
+ if ($trimmed !== "") {
+ $words = preg_split(self::$_whitespace_pattern, $trimmed);
+ $line->wc += count($words);
+ }
+
+ if ($add_line) {
+ $block->add_line();
+ }
+ }
+
+ /**
+ * Trim trailing white space from the frame text.
+ */
+ public function trim_trailing_ws(): void
+ {
+ $this->_frame->trim_trailing_ws();
+ }
+
+ public function reset(): void
+ {
+ parent::reset();
+ }
+
+ //........................................................................
+
+ public function get_min_max_width(): array
+ {
+ $fontMetrics = $this->getFontMetrics();
+ $frame = $this->_frame;
+ $style = $frame->get_style();
+
+ // Handle text transform and white space
+ $frame->set_text($this->pre_process_text($frame->get_text()));
+
+ // map text to fonts based on supported Unicode range
+ $frame->apply_font_mapping();
+ $text = $frame->get_text();
+
+ $font = $style->font_family;
+ $size = $style->font_size;
+ $word_spacing = $style->word_spacing;
+ $letter_spacing = $style->letter_spacing;
+
+ if (!$frame->is_pre()) {
+ // Determine whether the frame is at the start of its parent block.
+ // Trim leading white space in that case
+ $child = $frame;
+ $p = $frame->get_parent();
+ while (!$p->is_block() && !$child->get_prev_sibling()) {
+ $child = $p;
+ $p = $p->get_parent();
+ }
+
+ if (!$child->get_prev_sibling()) {
+ $text = ltrim($text, " ");
+ }
+
+ // Determine whether the frame is at the end of its parent block.
+ // Trim trailing white space in that case
+ $child = $frame;
+ $p = $frame->get_parent();
+ while (!$p->is_block() && !$child->get_next_sibling()) {
+ $child = $p;
+ $p = $p->get_parent();
+ }
+
+ if (!$child->get_next_sibling()) {
+ $text = rtrim($text, " ");
+ }
+ }
+
+ // Strip soft hyphens for max-line-width calculations
+ $visible_text = preg_replace('/\xAD/u', "", $text);
+
+ // Determine minimum text width
+ switch ($style->white_space) {
+ default:
+ case "normal":
+ case "pre-line":
+ case "pre-wrap":
+ // The min width is the longest word or, if breaking words is
+ // allowed with the `anywhere` keyword, the widest character.
+ // For performance reasons, we only check the first character in
+ // the latter case.
+ // https://www.w3.org/TR/css-text-3/#overflow-wrap-property
+ if ($style->overflow_wrap === "anywhere") {
+ $char = mb_substr($visible_text, 0, 1, "UTF-8");
+ $min = $fontMetrics->getTextWidth($char, $font, $size, $word_spacing, $letter_spacing);
+ } else {
+ // Find the longest word
+ $words = preg_split(self::$_wordbreak_pattern, $text, -1, PREG_SPLIT_DELIM_CAPTURE);
+ $lengths = array_map(function ($chunk) use ($fontMetrics, $font, $size, $word_spacing, $letter_spacing) {
+ // Allow trailing white space to overflow. As in actual
+ // layout above, only handle a single space for now
+ $sep = $chunk[1] ?? "";
+ $word = $sep === " " ? $chunk[0] : $chunk[0] . $sep;
+ return $fontMetrics->getTextWidth($word, $font, $size, $word_spacing, $letter_spacing);
+ }, array_chunk($words, 2));
+ $min = max($lengths);
+ }
+ break;
+
+ case "pre":
+ // Find the longest line
+ $lines = array_flip(preg_split("/\R/u", $visible_text));
+ array_walk($lines, function (&$chunked_text_width, $chunked_text) use ($fontMetrics, $font, $size, $word_spacing, $letter_spacing) {
+ $chunked_text_width = $fontMetrics->getTextWidth($chunked_text, $font, $size, $word_spacing, $letter_spacing);
+ });
+ arsort($lines);
+ $min = reset($lines);
+ break;
+
+ case "nowrap":
+ $min = $fontMetrics->getTextWidth($visible_text, $font, $size, $word_spacing, $letter_spacing);
+ break;
+ }
+
+ // Determine maximum text width
+ switch ($style->white_space) {
+ default:
+ case "normal":
+ $max = $fontMetrics->getTextWidth($visible_text, $font, $size, $word_spacing, $letter_spacing);
+ break;
+
+ case "pre-line":
+ case "pre-wrap":
+ // Find the longest line
+ $lines = array_flip(preg_split("/\R/u", $visible_text));
+ array_walk($lines, function (&$chunked_text_width, $chunked_text) use ($fontMetrics, $font, $size, $word_spacing, $letter_spacing) {
+ $chunked_text_width = $fontMetrics->getTextWidth($chunked_text, $font, $size, $word_spacing, $letter_spacing);
+ });
+ arsort($lines);
+ $max = reset($lines);
+ break;
+
+ case "pre":
+ case "nowrap":
+ $max = $min;
+ break;
+ }
+
+ // Account for margins, borders, and padding
+ $dims = [
+ $style->padding_left,
+ $style->padding_right,
+ $style->border_left_width,
+ $style->border_right_width,
+ $style->margin_left,
+ $style->margin_right
+ ];
+
+ // The containing block is not defined yet, treat percentages as 0
+ $delta = (float) $style->length_in_pt($dims, 0);
+ $min += $delta;
+ $max += $delta;
+
+ return [$min, $max, "min" => $min, "max" => $max];
+ }
+
+ /**
+ * @param FontMetrics $fontMetrics
+ * @return $this
+ */
+ public function setFontMetrics(FontMetrics $fontMetrics)
+ {
+ $this->fontMetrics = $fontMetrics;
+ return $this;
+ }
+
+ /**
+ * @return FontMetrics
+ */
+ public function getFontMetrics()
+ {
+ return $this->fontMetrics;
+ }
+}
diff --git a/app/libs/dompdf/vendor/dompdf/dompdf/src/Helpers.php b/app/libs/dompdf/vendor/dompdf/dompdf/src/Helpers.php
new file mode 100644
index 0000000..e8435d2
--- /dev/null
+++ b/app/libs/dompdf/vendor/dompdf/dompdf/src/Helpers.php
@@ -0,0 +1,1253 @@
+ tags if the current sapi is not 'cli'.
+ * Returns the output string instead of displaying it if $return is true.
+ *
+ * @param mixed $mixed variable or expression to display
+ * @param bool $return
+ *
+ * @return string|null
+ */
+ public static function pre_r($mixed, $return = false)
+ {
+ if ($return) {
+ return "" . print_r($mixed, true) . "
";
+ }
+
+ if (php_sapi_name() !== "cli") {
+ echo "";
+ }
+
+ print_r($mixed);
+
+ if (php_sapi_name() !== "cli") {
+ echo "";
+ } else {
+ echo "\n";
+ }
+
+ flush();
+
+ return null;
+ }
+
+ /**
+ * Builds a full url given a protocol, hostname, base path and URL.
+ *
+ * When the URL provided is a local file reference from the root of the filesystem
+ * (i.e., beginning with a "/") and the file does not resolve to a valid path,
+ * the path is validated against the chroot paths (if provided).
+ *
+ * @param string $protocol
+ * @param string $host
+ * @param string $base_path
+ * @param string $url
+ * @param array $chrootDirs array of strings representing the chroot paths
+ * @return string
+ */
+ public static function build_url($protocol, $host, $base_path, $url, $chrootDirs = [])
+ {
+ $protocol = mb_strtolower($protocol, "UTF-8");
+ if (empty($protocol)) {
+ $protocol = "file://";
+ }
+ if ($url === "") {
+ return null;
+ }
+
+ $url_lc = mb_strtolower($url, "UTF-8");
+
+ // Is the url already fully qualified, a Data URI, or a reference to a named anchor?
+ // File-protocol URLs may require additional processing (e.g. for URLs with a relative path)
+ if (
+ (
+ mb_strpos($url_lc, "://") !== false
+ && !in_array(substr($url_lc, 0, 7), ["file://", "phar://"], true)
+ )
+ || mb_substr($url_lc, 0, 1) === "#"
+ || mb_strpos($url_lc, "data:") === 0
+ || mb_strpos($url_lc, "mailto:") === 0
+ || mb_strpos($url_lc, "tel:") === 0
+ ) {
+ return $url;
+ }
+
+ $res = "";
+ if (strpos($url_lc, "file://") === 0) {
+ $url = substr($url, 7);
+ $protocol = "file://";
+ } elseif (strpos($url_lc, "phar://") === 0) {
+ $res = substr($url, strpos($url_lc, ".phar")+5);
+ $url = substr($url, 7, strpos($url_lc, ".phar")-2);
+ $protocol = "phar://";
+ }
+
+ $ret = "";
+
+ $is_local_path = in_array($protocol, ["file://", "phar://"], true);
+
+ if ($is_local_path) {
+ //On Windows local file, an abs path can begin also with a '\' or a drive letter and colon
+ //drive: followed by a relative path would be a drive specific default folder.
+ //not known in php app code, treat as abs path
+ //($url[1] !== ':' || ($url[2]!=='\\' && $url[2]!=='/'))
+ if ($url[0] !== '/' && (strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN' || (mb_strlen($url) > 1 && $url[0] !== '\\' && $url[1] !== ':'))) {
+ // For rel path and local access we ignore the host, and run the path through realpath()
+ $ret .= realpath($base_path) . '/';
+ }
+ $ret .= $url;
+ $ret = preg_replace('/\?(.*)$/', "", $ret);
+
+ $filepath = realpath($ret);
+ if ($filepath !== false) {
+ $ret = "$protocol$filepath$res";
+
+ return $ret;
+ }
+
+ if ($url[0] == '/' && !empty($chrootDirs)) {
+ foreach ($chrootDirs as $dir) {
+ $ret = realpath($dir) . $url;
+ $ret = preg_replace('/\?(.*)$/', "", $ret);
+
+ if ($filepath = realpath($ret)) {
+ $ret = "$protocol$filepath$res";
+
+ return $ret;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ $ret = $protocol;
+ // Protocol relative urls (e.g. "//example.org/style.css")
+ if (strpos($url, '//') === 0) {
+ $ret .= substr($url, 2);
+ //remote urls with backslash in html/css are not really correct, but lets be genereous
+ } elseif ($url[0] === '/' || $url[0] === '\\') {
+ // Absolute path
+ $ret .= $host . $url;
+ } else {
+ // Relative path
+ //$base_path = $base_path !== "" ? rtrim($base_path, "/\\") . "/" : "";
+ $ret .= $host . $base_path . $url;
+ }
+
+ // URL should now be complete, final cleanup
+ $parsed_url = parse_url($ret);
+
+ // reproduced from https://www.php.net/manual/en/function.parse-url.php#106731
+ $scheme = isset($parsed_url['scheme']) ? $parsed_url['scheme'] . '://' : '';
+ $host = isset($parsed_url['host']) ? $parsed_url['host'] : '';
+ $port = isset($parsed_url['port']) ? ':' . $parsed_url['port'] : '';
+ $user = isset($parsed_url['user']) ? $parsed_url['user'] : '';
+ $pass = isset($parsed_url['pass']) ? ':' . $parsed_url['pass'] : '';
+ $pass = ($user || $pass) ? "$pass@" : '';
+ $path = isset($parsed_url['path']) ? $parsed_url['path'] : '';
+ $query = isset($parsed_url['query']) ? '?' . $parsed_url['query'] : '';
+ $fragment = isset($parsed_url['fragment']) ? '#' . $parsed_url['fragment'] : '';
+
+ // partially reproduced from https://stackoverflow.com/a/1243431/264628
+ /* replace '//' or '/./' or '/foo/../' with '/' */
+ $re = array('#(/\.?/)#', '#/(?!\.\.)[^/]+/\.\./#');
+ for ($n=1; $n>0; $path=preg_replace($re, '/', $path, -1, $n)) {}
+
+ $ret = "$scheme$user$pass$host$port$path$query$fragment";
+
+ return $ret;
+ }
+
+ /**
+ * Builds a HTTP Content-Disposition header string using `$dispositionType`
+ * and `$filename`.
+ *
+ * If the filename contains any characters not in the ISO-8859-1 character
+ * set, a fallback filename will be included for clients not supporting the
+ * `filename*` parameter.
+ *
+ * @param string $dispositionType
+ * @param string $filename
+ * @return string
+ */
+ public static function buildContentDispositionHeader($dispositionType, $filename)
+ {
+ $encoding = mb_detect_encoding($filename);
+ $fallbackfilename = mb_convert_encoding($filename, "ISO-8859-1", $encoding);
+ $fallbackfilename = str_replace("\"", "", $fallbackfilename);
+ $encodedfilename = rawurlencode($filename);
+
+ $contentDisposition = "Content-Disposition: $dispositionType; filename=\"$fallbackfilename\"";
+ if ($fallbackfilename !== $filename) {
+ $contentDisposition .= "; filename*=UTF-8''$encodedfilename";
+ }
+
+ return $contentDisposition;
+ }
+
+ /**
+ * Converts decimal numbers to roman numerals.
+ *
+ * As numbers larger than 3999 (and smaller than 1) cannot be represented in
+ * the standard form of roman numerals, those are left in decimal form.
+ *
+ * See https://en.wikipedia.org/wiki/Roman_numerals#Standard_form
+ *
+ * @param int|string $num
+ *
+ * @throws Exception
+ * @return string
+ */
+ public static function dec2roman($num): string
+ {
+
+ static $ones = ["", "i", "ii", "iii", "iv", "v", "vi", "vii", "viii", "ix"];
+ static $tens = ["", "x", "xx", "xxx", "xl", "l", "lx", "lxx", "lxxx", "xc"];
+ static $hund = ["", "c", "cc", "ccc", "cd", "d", "dc", "dcc", "dccc", "cm"];
+ static $thou = ["", "m", "mm", "mmm"];
+
+ if (!is_numeric($num)) {
+ throw new Exception("dec2roman() requires a numeric argument.");
+ }
+
+ if ($num >= 4000 || $num <= 0) {
+ return (string) $num;
+ }
+
+ $num = strrev((string)$num);
+
+ $ret = "";
+ switch (mb_strlen($num)) {
+ /** @noinspection PhpMissingBreakStatementInspection */
+ case 4:
+ $ret .= $thou[$num[3]];
+ /** @noinspection PhpMissingBreakStatementInspection */
+ case 3:
+ $ret .= $hund[$num[2]];
+ /** @noinspection PhpMissingBreakStatementInspection */
+ case 2:
+ $ret .= $tens[$num[1]];
+ /** @noinspection PhpMissingBreakStatementInspection */
+ case 1:
+ $ret .= $ones[$num[0]];
+ default:
+ break;
+ }
+
+ return $ret;
+ }
+
+ /**
+ * Converts decimal numbers to base26 (hexavigesimal)
+ * represented in lower case letters.
+ *
+ * @param int|string $num
+ *
+ * @throws Exception
+ * @return string
+ */
+ public static function dec2base26($num): string
+ {
+ if (!is_numeric($num)) {
+ throw new Exception("dec2base26() requires a numeric argument.");
+ }
+
+ $num = intval($num);
+
+ if ($num <= 0) {
+ return (string) $num;
+ }
+
+ $ret = '';
+ while ($num > 0) {
+ $remainder = ($num - 1) % 26;
+ $ret = chr(97 + $remainder) . $ret;
+ $num = intval(($num - 1) / 26);
+ }
+ return $ret;
+ }
+
+ /**
+ * Restrict a length to the given range.
+ *
+ * If min > max, the result is min.
+ *
+ * @param float $length
+ * @param float $min
+ * @param float $max
+ *
+ * @return float
+ */
+ public static function clamp(float $length, float $min, float $max): float
+ {
+ return max($min, min($length, $max));
+ }
+
+ /**
+ * Determines whether $value is a percentage or not
+ *
+ * @param string|float|int $value
+ *
+ * @return bool
+ */
+ public static function is_percent($value): bool
+ {
+ return is_string($value) && false !== mb_strpos($value, "%");
+ }
+
+ /**
+ * Parses a data URI scheme
+ * http://en.wikipedia.org/wiki/Data_URI_scheme
+ *
+ * @param string $data_uri The data URI to parse
+ *
+ * @return array|bool The result with charset, mime type and decoded data
+ */
+ public static function parse_data_uri($data_uri)
+ {
+ $expression = '/^data:(?P[a-z0-9\/+-.]+)(;charset=(?P[a-z0-9-])+)?(?P;base64)?\,(?P.*)?/is';
+ if (!preg_match($expression, $data_uri, $match)) {
+ $parts = explode(",", $data_uri);
+ $parts[0] = preg_replace('/\\s/', '', $parts[0]);
+ if (preg_match('/\\s/', $data_uri) && !preg_match($expression, implode(",", $parts), $match)) {
+ return false;
+ }
+ }
+
+ $match['data'] = rawurldecode($match['data']);
+ $result = [
+ 'charset' => $match['charset'] ? $match['charset'] : 'US-ASCII',
+ 'mime' => $match['mime'] ? $match['mime'] : 'text/plain',
+ 'data' => $match['base64'] ? base64_decode($match['data']) : $match['data'],
+ ];
+
+ return $result;
+ }
+
+ /**
+ * Encodes a Uniform Resource Identifier (URI) by replacing non-alphanumeric
+ * characters with a percent (%) sign followed by two hex digits, excepting
+ * characters in the URI reserved character set.
+ *
+ * Assumes that the URI is a complete URI, so does not encode reserved
+ * characters that have special meaning in the URI.
+ *
+ * Simulates the encodeURI function available in JavaScript
+ * https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/encodeURI
+ *
+ * Source: http://stackoverflow.com/q/4929584/264628
+ *
+ * @param string $uri The URI to encode
+ * @return string The original URL with special characters encoded
+ */
+ public static function encodeURI($uri) {
+ $unescaped = [
+ '%2D'=>'-','%5F'=>'_','%2E'=>'.','%21'=>'!', '%7E'=>'~',
+ '%2A'=>'*', '%27'=>"'", '%28'=>'(', '%29'=>')'
+ ];
+ $reserved = [
+ '%3B'=>';','%2C'=>',','%2F'=>'/','%3F'=>'?','%3A'=>':',
+ '%40'=>'@','%26'=>'&','%3D'=>'=','%2B'=>'+','%24'=>'$'
+ ];
+ $score = [
+ '%23'=>'#'
+ ];
+ return preg_replace(
+ '/%25([a-fA-F0-9]{2,2})/',
+ '%$1',
+ strtr(rawurlencode($uri), array_merge($reserved, $unescaped, $score))
+ );
+ }
+
+ /**
+ * Decoder for RLE8 compression in windows bitmaps
+ * http://msdn.microsoft.com/library/default.asp?url=/library/en-us/gdi/bitmaps_6x0u.asp
+ *
+ * @param string $str Data to decode
+ * @param int $width Image width
+ *
+ * @return string
+ */
+ public static function rle8_decode($str, $width)
+ {
+ $lineWidth = $width + (3 - ($width - 1) % 4);
+ $out = '';
+ $cnt = strlen($str);
+
+ for ($i = 0; $i < $cnt; $i++) {
+ $o = ord($str[$i]);
+ switch ($o) {
+ case 0: # ESCAPE
+ $i++;
+ switch (ord($str[$i])) {
+ case 0: # NEW LINE
+ $padCnt = $lineWidth - strlen($out) % $lineWidth;
+ if ($padCnt < $lineWidth) {
+ $out .= str_repeat(chr(0), $padCnt); # pad line
+ }
+ break;
+ case 1: # END OF FILE
+ $padCnt = $lineWidth - strlen($out) % $lineWidth;
+ if ($padCnt < $lineWidth) {
+ $out .= str_repeat(chr(0), $padCnt); # pad line
+ }
+ break 3;
+ case 2: # DELTA
+ $i += 2;
+ break;
+ default: # ABSOLUTE MODE
+ $num = ord($str[$i]);
+ for ($j = 0; $j < $num; $j++) {
+ $out .= $str[++$i];
+ }
+ if ($num % 2) {
+ $i++;
+ }
+ }
+ break;
+ default:
+ $out .= str_repeat($str[++$i], $o);
+ }
+ }
+ return $out;
+ }
+
+ /**
+ * Decoder for RLE4 compression in windows bitmaps
+ * see http://msdn.microsoft.com/library/default.asp?url=/library/en-us/gdi/bitmaps_6x0u.asp
+ *
+ * @param string $str Data to decode
+ * @param int $width Image width
+ *
+ * @return string
+ */
+ public static function rle4_decode($str, $width)
+ {
+ $w = floor($width / 2) + ($width % 2);
+ $lineWidth = $w + (3 - (($width - 1) / 2) % 4);
+ $pixels = [];
+ $cnt = strlen($str);
+ $c = 0;
+
+ for ($i = 0; $i < $cnt; $i++) {
+ $o = ord($str[$i]);
+ switch ($o) {
+ case 0: # ESCAPE
+ $i++;
+ switch (ord($str[$i])) {
+ case 0: # NEW LINE
+ while (count($pixels) % $lineWidth != 0) {
+ $pixels[] = 0;
+ }
+ break;
+ case 1: # END OF FILE
+ while (count($pixels) % $lineWidth != 0) {
+ $pixels[] = 0;
+ }
+ break 3;
+ case 2: # DELTA
+ $i += 2;
+ break;
+ default: # ABSOLUTE MODE
+ $num = ord($str[$i]);
+ for ($j = 0; $j < $num; $j++) {
+ if ($j % 2 == 0) {
+ $c = ord($str[++$i]);
+ $pixels[] = ($c & 240) >> 4;
+ } else {
+ $pixels[] = $c & 15;
+ }
+ }
+
+ if ($num % 2 == 0) {
+ $i++;
+ }
+ }
+ break;
+ default:
+ $c = ord($str[++$i]);
+ for ($j = 0; $j < $o; $j++) {
+ $pixels[] = ($j % 2 == 0 ? ($c & 240) >> 4 : $c & 15);
+ }
+ }
+ }
+
+ $out = '';
+ if (count($pixels) % 2) {
+ $pixels[] = 0;
+ }
+
+ $cnt = count($pixels) / 2;
+
+ for ($i = 0; $i < $cnt; $i++) {
+ $out .= chr(16 * $pixels[2 * $i] + $pixels[2 * $i + 1]);
+ }
+
+ return $out;
+ }
+
+ /**
+ * parse a full url or pathname and return an array(protocol, host, path,
+ * file + query + fragment)
+ *
+ * @param string $url
+ * @return array
+ */
+ public static function explode_url($url)
+ {
+ $protocol = "";
+ $host = "";
+ $path = "";
+ $file = "";
+ $res = "";
+
+ $arr = parse_url($url);
+ if ( isset($arr["scheme"]) ) {
+ $arr["scheme"] = mb_strtolower($arr["scheme"], "UTF-8");
+ }
+
+ if (isset($arr["scheme"]) && $arr["scheme"] !== "file" && $arr["scheme"] !== "phar" && strlen($arr["scheme"]) > 1) {
+ $protocol = $arr["scheme"] . "://";
+
+ if (isset($arr["user"])) {
+ $host .= $arr["user"];
+
+ if (isset($arr["pass"])) {
+ $host .= ":" . $arr["pass"];
+ }
+
+ $host .= "@";
+ }
+
+ if (isset($arr["host"])) {
+ $host .= $arr["host"];
+ }
+
+ if (isset($arr["port"])) {
+ $host .= ":" . $arr["port"];
+ }
+
+ if (isset($arr["path"]) && $arr["path"] !== "") {
+ // Do we have a trailing slash?
+ if ($arr["path"][mb_strlen($arr["path"], "8bit") - 1] === "/") {
+ $path = $arr["path"];
+ $file = "";
+ } else {
+ $path = rtrim(dirname($arr["path"]), '/\\') . "/";
+ $file = basename($arr["path"]);
+ }
+ }
+
+ if (isset($arr["query"])) {
+ $file .= "?" . $arr["query"];
+ }
+
+ if (isset($arr["fragment"])) {
+ $file .= "#" . $arr["fragment"];
+ }
+
+ } else {
+
+ $protocol = "";
+ $host = ""; // localhost, really
+
+ $i = mb_stripos($url, "://", 0, "UTF-8");
+ if ($i !== false) {
+ $protocol = mb_strtolower(mb_substr($url, 0, $i + 3, "UTF-8"), "UTF-8");
+ $url = mb_substr($url, $i + 3, null, "UTF-8");
+ } else {
+ $protocol = "file://";
+ }
+
+ if ($protocol === "phar://") {
+ $res = substr($url, stripos($url, ".phar")+5);
+ $url = substr($url, 7, stripos($url, ".phar")-2);
+ }
+
+ $file = basename($url);
+ $path = dirname($url) . "/";
+ }
+
+ $ret = [$protocol, $host, $path, $file,
+ "protocol" => $protocol,
+ "host" => $host,
+ "path" => $path,
+ "file" => $file,
+ "resource" => $res];
+ return $ret;
+ }
+
+ /**
+ * Print debug messages
+ *
+ * @param string $type The type of debug messages to print
+ * @param string $msg The message to show
+ */
+ public static function dompdf_debug($type, $msg)
+ {
+ global $_DOMPDF_DEBUG_TYPES, $_dompdf_show_warnings, $_dompdf_debug;
+ if (isset($_DOMPDF_DEBUG_TYPES[$type]) && ($_dompdf_show_warnings || $_dompdf_debug)) {
+ $arr = debug_backtrace();
+
+ echo basename($arr[0]["file"]) . " (" . $arr[0]["line"] . "): " . $arr[1]["function"] . ": ";
+ Helpers::pre_r($msg);
+ }
+ }
+
+ /**
+ * Stores warnings in an array for display later
+ * This function allows warnings generated by the DomDocument parser
+ * and CSS loader ({@link Stylesheet}) to be captured and displayed
+ * later. Without this function, errors are displayed immediately and
+ * PDF streaming is impossible.
+ * @see http://www.php.net/manual/en/function.set-error_handler.php
+ *
+ * @param int $errno
+ * @param string $errstr
+ * @param string $errfile
+ * @param string $errline
+ *
+ * @throws Exception
+ */
+ public static function record_warnings($errno, $errstr, $errfile, $errline)
+ {
+ // Not a warning or notice
+ if (!($errno & (E_WARNING | E_NOTICE | E_USER_NOTICE | E_USER_WARNING | E_DEPRECATED | E_USER_DEPRECATED))) {
+ throw new Exception($errstr . " $errno");
+ }
+
+ global $_dompdf_warnings;
+ global $_dompdf_show_warnings;
+
+ if ($_dompdf_show_warnings) {
+ echo $errstr . "\n";
+ }
+
+ $_dompdf_warnings[] = $errstr;
+ }
+
+ /**
+ * Get Unicode code point of character
+ *
+ * Shim for use on systems running PHP < 7.2
+ *
+ * @param string $c
+ * @param string|null $encoding
+ * @return int|false
+ */
+ public static function uniord(string $c, ?string $encoding = null)
+ {
+ if (function_exists("mb_ord")) {
+ if (PHP_VERSION_ID < 80000 && $encoding === null) {
+ // in PHP < 8 the encoding argument, if supplied, must be a valid encoding
+ $encoding = "UTF-8";
+ }
+ return mb_ord($c, $encoding);
+ }
+
+ if ($encoding != "UTF-8" && $encoding !== null) {
+ $c = mb_convert_encoding($c, "UTF-8", $encoding);
+ }
+
+ $length = mb_strlen(mb_substr($c, 0, 1, "UTF-8"), "8bit");
+ $ord = false;
+ $bytes = [];
+ $numbytes = 1;
+ for ($i = 0; $i < $length; $i++) {
+ $o = ord($c[$i]); // get one string character at time
+ if (count($bytes) === 0) { // get starting octect
+ if ($o <= 0x7F) {
+ $ord = $o;
+ $numbytes = 1;
+ } elseif (($o >> 0x05) === 0x06) { // 2 bytes character (0x06 = 110 BIN)
+ $bytes[] = ($o - 0xC0) << 0x06;
+ $numbytes = 2;
+ } elseif (($o >> 0x04) === 0x0E) { // 3 bytes character (0x0E = 1110 BIN)
+ $bytes[] = ($o - 0xE0) << 0x0C;
+ $numbytes = 3;
+ } elseif (($o >> 0x03) === 0x1E) { // 4 bytes character (0x1E = 11110 BIN)
+ $bytes[] = ($o - 0xF0) << 0x12;
+ $numbytes = 4;
+ } else {
+ $ord = false;
+ break;
+ }
+ } elseif (($o >> 0x06) === 0x02) { // bytes 2, 3 and 4 must start with 0x02 = 10 BIN
+ $bytes[] = $o - 0x80;
+ if (count($bytes) === $numbytes) {
+ // compose UTF-8 bytes to a single unicode value
+ $o = $bytes[0];
+ for ($j = 1; $j < $numbytes; $j++) {
+ $o += ($bytes[$j] << (($numbytes - $j - 1) * 0x06));
+ }
+ if ((($o >= 0xD800) and ($o <= 0xDFFF)) or ($o >= 0x10FFFF)) {
+ // The definition of UTF-8 prohibits encoding character numbers between
+ // U+D800 and U+DFFF, which are reserved for use with the UTF-16
+ // encoding form (as surrogate pairs) and do not directly represent
+ // characters.
+ return false;
+ } else {
+ $ord = $o; // add char to array
+ }
+ // reset data for next char
+ $bytes = [];
+ $numbytes = 1;
+ }
+ } else {
+ $ord = false;
+ break;
+ }
+ }
+
+ return $ord;
+ }
+
+ /**
+ * Return character by Unicode code point value
+ *
+ * Shim for use on systems running PHP < 7.2
+ *
+ * @param int $c
+ * @param string|null $encoding
+ * @return string|false
+ */
+ public static function unichr(int $c, ?string $encoding = null)
+ {
+ if (function_exists("mb_chr")) {
+ if (PHP_VERSION_ID < 80000 && $encoding === null) {
+ // in PHP < 8 the encoding argument, if supplied, must be a valid encoding
+ $encoding = "UTF-8";
+ }
+ return mb_chr($c, $encoding);
+ }
+
+ $chr = false;
+ if ($c <= 0x7F) {
+ $chr = chr($c);
+ } elseif ($c <= 0x7FF) {
+ $chr = chr(0xC0 | $c >> 6) . chr(0x80 | $c & 0x3F);
+ } elseif ($c <= 0xFFFF) {
+ $chr = chr(0xE0 | $c >> 12) . chr(0x80 | $c >> 6 & 0x3F)
+ . chr(0x80 | $c & 0x3F);
+ } elseif ($c <= 0x10FFFF) {
+ $chr = chr(0xF0 | $c >> 18) . chr(0x80 | $c >> 12 & 0x3F)
+ . chr(0x80 | $c >> 6 & 0x3F)
+ . chr(0x80 | $c & 0x3F);
+ }
+
+ return $chr;
+ }
+
+ /**
+ * Converts a CMYK color to RGB
+ *
+ * @param float|float[] $c
+ * @param float $m
+ * @param float $y
+ * @param float $k
+ *
+ * @return float[]
+ */
+ public static function cmyk_to_rgb($c, $m = null, $y = null, $k = null)
+ {
+ if (is_array($c)) {
+ [$c, $m, $y, $k] = $c;
+ }
+
+ $c *= 255;
+ $m *= 255;
+ $y *= 255;
+ $k *= 255;
+
+ $r = (1 - round(2.55 * ($c + $k)));
+ $g = (1 - round(2.55 * ($m + $k)));
+ $b = (1 - round(2.55 * ($y + $k)));
+
+ if ($r < 0) {
+ $r = 0;
+ }
+ if ($g < 0) {
+ $g = 0;
+ }
+ if ($b < 0) {
+ $b = 0;
+ }
+
+ return [
+ $r, $g, $b,
+ "r" => $r, "g" => $g, "b" => $b
+ ];
+ }
+
+ /**
+ * getimagesize doesn't give a good size for 32bit BMP image v5
+ *
+ * @param string $filename
+ * @param resource $context
+ * @return array An array of three elements: width and height as
+ * `float|int`, and image type as `string|null`.
+ */
+ public static function dompdf_getimagesize($filename, $context = null)
+ {
+ static $cache = [];
+
+ // Custom types
+ $types = [
+ IMAGETYPE_JPEG => "jpeg",
+ IMAGETYPE_GIF => "gif",
+ IMAGETYPE_BMP => "bmp",
+ IMAGETYPE_PNG => "png",
+ IMAGETYPE_WEBP => "webp"
+ ];
+ if (defined('IMAGETYPE_SVG')) {
+ $types[IMAGETYPE_SVG] = "svg";
+ }
+
+ if (isset($cache[$filename])) {
+ return $cache[$filename];
+ }
+
+ $parse_result = @getimagesize($filename);
+ $width = $height = $type = null;
+ if ($parse_result !== false) {
+ [$width, $height, $type] = $parse_result;
+ $type = $types[$type] ?? null;
+ }
+
+ if ($width == null || $height == null) {
+ [$data] = Helpers::getFileContent($filename, $context);
+
+ if ($data !== null) {
+ if (substr($data, 0, 2) === "BM") {
+ $meta = unpack("vtype/Vfilesize/Vreserved/Voffset/Vheadersize/Vwidth/Vheight", $data);
+ $width = (int) $meta["width"];
+ $height = (int) $meta["height"];
+ $type = "bmp";
+ } elseif (strpos($data, "