xref: /plugin/dw2pdf/src/Writer.php (revision 6444f9a19e8eb11cea8df23eb06057f6078ff510)
1c5c184fdSAndreas Gohr<?php
2c5c184fdSAndreas Gohr
3c5c184fdSAndreas Gohrnamespace dokuwiki\plugin\dw2pdf\src;
4c5c184fdSAndreas Gohr
5bbcdb3feSAndreas Gohruse DOMDocument;
61d334a8cSAndreas Gohruse dokuwiki\ErrorHandler;
7c5c184fdSAndreas Gohruse Mpdf\HTMLParserMode;
8c5c184fdSAndreas Gohruse Mpdf\MpdfException;
9c5c184fdSAndreas Gohr
10c5c184fdSAndreas Gohrclass Writer
11c5c184fdSAndreas Gohr{
121a52a777SAnna Dabrowska    /** @var DokuMpdf Our MPDF instance */
131a52a777SAnna Dabrowska    protected DokuMpdf $mpdf;
141d334a8cSAndreas Gohr
150eefd8f7SAndreas Gohr    /** @var Config The configuration */
160eefd8f7SAndreas Gohr    protected Config $config;
170eefd8f7SAndreas Gohr
181d334a8cSAndreas Gohr    /** @var Template The template used */
19c5c184fdSAndreas Gohr    protected Template $template;
201d334a8cSAndreas Gohr
21081fc334SAndreas Gohr    /** @var Styles The style parser */
22081fc334SAndreas Gohr    protected Styles $styles;
23081fc334SAndreas Gohr
241d334a8cSAndreas Gohr    /** @var bool Signal to output a page break before the next output */
25c5c184fdSAndreas Gohr    protected bool $breakBeforeNext = false;
26c5c184fdSAndreas Gohr
271d334a8cSAndreas Gohr    /** @var bool Are we debugging? */
281d334a8cSAndreas Gohr    protected bool $debug = false;
291d334a8cSAndreas Gohr
301d334a8cSAndreas Gohr    /** @var string Store HTML when debugging */
311d334a8cSAndreas Gohr    protected string $debugHTML = '';
321d334a8cSAndreas Gohr
33f77f381aSAndreas Gohr    /** @var false Is this the first page being written? */
34f77f381aSAndreas Gohr    protected bool $isFirstPage = true;
35f77f381aSAndreas Gohr
36c5c184fdSAndreas Gohr    /**
371a52a777SAnna Dabrowska     * @param DokuMpdf $mpdf
38c5c184fdSAndreas Gohr     * @param Template $template
39f2000117SAndreas Gohr     * @param Styles $styles
40c5c184fdSAndreas Gohr     */
411a52a777SAnna Dabrowska    public function __construct(DokuMpdf $mpdf, Config $config, Template $template, Styles $styles)
42c5c184fdSAndreas Gohr    {
43c5c184fdSAndreas Gohr        $this->mpdf = $mpdf;
440eefd8f7SAndreas Gohr        $this->config = $config;
45c5c184fdSAndreas Gohr        $this->template = $template;
46081fc334SAndreas Gohr        $this->styles = $styles;
47f2000117SAndreas Gohr        $this->debug = $config->isDebugEnabled();
48bee95f00SAndreas Gohr
49b08d5470SAndreas Gohr        /**
50b08d5470SAndreas Gohr         * initialize a new renderer instance (singleton instance will be reused in later p_* calls)
51b08d5470SAndreas Gohr         * @var \renderer_plugin_dw2pdf $renderer
52b08d5470SAndreas Gohr         */
53bee95f00SAndreas Gohr        $renderer = plugin_load('renderer', 'dw2pdf', true);
54b08d5470SAndreas Gohr        $renderer->setConfig($config);
55c5c184fdSAndreas Gohr    }
56c5c184fdSAndreas Gohr
57c5c184fdSAndreas Gohr    /**
58c5c184fdSAndreas Gohr     * Initialize the document
59c5c184fdSAndreas Gohr     *
60c5c184fdSAndreas Gohr     * @param string $title
61c5c184fdSAndreas Gohr     * @return void
62c5c184fdSAndreas Gohr     * @throws MpdfException
63c5c184fdSAndreas Gohr     */
64c5c184fdSAndreas Gohr    public function startDocument(string $title): void
65c5c184fdSAndreas Gohr    {
66c5c184fdSAndreas Gohr        $this->mpdf->SetTitle($title);
67c5c184fdSAndreas Gohr
68081fc334SAndreas Gohr        // Set the styles
69c5c184fdSAndreas Gohr        $styles = '@page landscape-page { size:landscape }';
70c5c184fdSAndreas Gohr        $styles .= 'div.dw2pdf-landscape { page:landscape-page }';
71c5c184fdSAndreas Gohr        $styles .= '@page portrait-page { size:portrait }';
72c5c184fdSAndreas Gohr        $styles .= 'div.dw2pdf-portrait { page:portrait-page }';
73081fc334SAndreas Gohr        $styles .= $this->styles->getCSS();
741d334a8cSAndreas Gohr        $this->write($styles, HTMLParserMode::HEADER_CSS);
75c5c184fdSAndreas Gohr
76c5c184fdSAndreas Gohr        //start body html
771d334a8cSAndreas Gohr        $this->write('<div class="dokuwiki">', HTMLParserMode::HTML_BODY, true, false);
78c5c184fdSAndreas Gohr    }
79c5c184fdSAndreas Gohr
80c5c184fdSAndreas Gohr    /**
81c5c184fdSAndreas Gohr     * Insert a page break
82c5c184fdSAndreas Gohr     *
83c5c184fdSAndreas Gohr     * @return void
84c5c184fdSAndreas Gohr     * @throws MpdfException
85c5c184fdSAndreas Gohr     */
86c5c184fdSAndreas Gohr    public function pageBreak(): void
87c5c184fdSAndreas Gohr    {
881d334a8cSAndreas Gohr        $this->write('<pagebreak />', 2, false, false);
89c5c184fdSAndreas Gohr    }
90c5c184fdSAndreas Gohr
91c5c184fdSAndreas Gohr    /**
92c5c184fdSAndreas Gohr     * Write a wiki page into the PDF
93c5c184fdSAndreas Gohr     *
94c5c184fdSAndreas Gohr     * @param string $html The rendered HTML of the wiki page
95c5c184fdSAndreas Gohr     * @return void
96c5c184fdSAndreas Gohr     * @throws MpdfException
97c5c184fdSAndreas Gohr     */
98c5c184fdSAndreas Gohr    public function wikiPage(string $html): void
99c5c184fdSAndreas Gohr    {
100c5c184fdSAndreas Gohr        $this->conditionalPageBreak();
101c5c184fdSAndreas Gohr        $this->applyHeaderFooters();
1021d334a8cSAndreas Gohr        $this->write($html, HTMLParserMode::HTML_BODY, false, false);
103c5c184fdSAndreas Gohr
104c5c184fdSAndreas Gohr        // add citation box if any
105c5c184fdSAndreas Gohr        $cite = $this->template->getHTML('citation');
106c5c184fdSAndreas Gohr        if ($cite) {
1071d334a8cSAndreas Gohr            $this->write($cite, HTMLParserMode::HTML_BODY, false, false);
108c5c184fdSAndreas Gohr        }
109c5c184fdSAndreas Gohr
110c5c184fdSAndreas Gohr        $this->breakAfterMe();
111c5c184fdSAndreas Gohr    }
112c5c184fdSAndreas Gohr
113b883aeceSAndreas Gohr    /**
114293d84e6SAndreas Gohr     * Render and write a wiki page into the PDF
115293d84e6SAndreas Gohr     *
116293d84e6SAndreas Gohr     * This caches the rendered page individually (unless a specific revision is requested). So even
117293d84e6SAndreas Gohr     * when PDF needs to be regenerated, pages that have not changed will be loaded from cache.
118293d84e6SAndreas Gohr     *
119293d84e6SAndreas Gohr     * @param AbstractCollector $collector The collector providing the page context
120293d84e6SAndreas Gohr     * @param string $pageId The page ID to render
121293d84e6SAndreas Gohr     * @return void
122293d84e6SAndreas Gohr     * @throws MpdfException
123293d84e6SAndreas Gohr     */
124293d84e6SAndreas Gohr    public function renderWikiPage(AbstractCollector $collector, string $pageId): void
125293d84e6SAndreas Gohr    {
126293d84e6SAndreas Gohr        $rev = $collector->getRev();
127293d84e6SAndreas Gohr        $at = $collector->getAt();
128*6444f9a1SAnna Dabrowska        $file = wikiFN($pageId, $rev);
129293d84e6SAndreas Gohr
130293d84e6SAndreas Gohr        //ensure $id is in global $ID (needed for parsing)
131293d84e6SAndreas Gohr        global $ID;
132293d84e6SAndreas Gohr        $keep = $ID;
133293d84e6SAndreas Gohr        $ID = $pageId;
134293d84e6SAndreas Gohr
135293d84e6SAndreas Gohr        if ($collector->getRev()) {
136293d84e6SAndreas Gohr            //no caching on old revisions
1370eefd8f7SAndreas Gohr            $html = p_render('dw2pdf', p_get_instructions(io_readWikiPage($file, $pageId, $rev)), $info, $at);
138293d84e6SAndreas Gohr        } else {
1390eefd8f7SAndreas Gohr            $html = p_cached_output($file, 'dw2pdf', $pageId);
140293d84e6SAndreas Gohr        }
141293d84e6SAndreas Gohr
142293d84e6SAndreas Gohr        //restore ID (just in case)
143293d84e6SAndreas Gohr        $ID = $keep;
144293d84e6SAndreas Gohr
1450eefd8f7SAndreas Gohr        // Fix internal links then write the page
1460eefd8f7SAndreas Gohr        $html = $this->fixInternalLinks($collector, $html);
1470eefd8f7SAndreas Gohr        $this->wikiPage($html);
1480eefd8f7SAndreas Gohr    }
1490eefd8f7SAndreas Gohr
1500eefd8f7SAndreas Gohr    /**
1510eefd8f7SAndreas Gohr     * If the given HTML contains internal links to pages that are part of the exported PDF,
1520eefd8f7SAndreas Gohr     * fix the links to point to the correct section within the PDF.
1530eefd8f7SAndreas Gohr     *
1540eefd8f7SAndreas Gohr     * @param AbstractCollector $collector
1550eefd8f7SAndreas Gohr     * @param string $html The rendered HTML of the wiki page
1560eefd8f7SAndreas Gohr     * @return string
1570eefd8f7SAndreas Gohr     */
1580eefd8f7SAndreas Gohr    protected function fixInternalLinks(AbstractCollector $collector, string $html): string
1590eefd8f7SAndreas Gohr    {
160bbcdb3feSAndreas Gohr        if ($html === '') return $html;
1610eefd8f7SAndreas Gohr
162bbcdb3feSAndreas Gohr        // quick bail out if the page has no internal link markers
163bbcdb3feSAndreas Gohr        if (!str_contains($html, 'data-dw2pdf-target')) {
1640eefd8f7SAndreas Gohr            return $html;
165293d84e6SAndreas Gohr        }
166293d84e6SAndreas Gohr
167bbcdb3feSAndreas Gohr        $pages = $collector->getPages();
168bbcdb3feSAndreas Gohr        if ($pages === []) {
169bbcdb3feSAndreas Gohr            return $html;
170bbcdb3feSAndreas Gohr        }
171bbcdb3feSAndreas Gohr        $pages = array_fill_keys($pages, true);
172bbcdb3feSAndreas Gohr
173bbcdb3feSAndreas Gohr        $dom = new DOMDocument('1.0', 'UTF-8');
174bbcdb3feSAndreas Gohr        $previous = libxml_use_internal_errors(true);
175bbcdb3feSAndreas Gohr        $loaded = $dom->loadHTML(
176bbcdb3feSAndreas Gohr            '<?xml encoding="utf-8"?>' . '<div>' . $html . '</div>',
177bbcdb3feSAndreas Gohr            LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD
178bbcdb3feSAndreas Gohr        );
179bbcdb3feSAndreas Gohr        libxml_clear_errors();
180bbcdb3feSAndreas Gohr        libxml_use_internal_errors($previous);
181bbcdb3feSAndreas Gohr
182bbcdb3feSAndreas Gohr        if (!$loaded) {
183bbcdb3feSAndreas Gohr            return $html;
184bbcdb3feSAndreas Gohr        }
185bbcdb3feSAndreas Gohr
186bbcdb3feSAndreas Gohr        $anchors = $dom->getElementsByTagName('a');
187bbcdb3feSAndreas Gohr        if ($anchors->length === 0) {
188bbcdb3feSAndreas Gohr            return $html;
189bbcdb3feSAndreas Gohr        }
190bbcdb3feSAndreas Gohr
191bbcdb3feSAndreas Gohr        $pageAnchors = [];
192bbcdb3feSAndreas Gohr        foreach ($anchors as $anchor) {
193bbcdb3feSAndreas Gohr            /** @var \DOMElement $anchor */
194bbcdb3feSAndreas Gohr            if (!$anchor->hasAttribute('data-dw2pdf-target')) {
195bbcdb3feSAndreas Gohr                continue;
196bbcdb3feSAndreas Gohr            }
197bbcdb3feSAndreas Gohr
198bbcdb3feSAndreas Gohr            $target = $anchor->getAttribute('data-dw2pdf-target');
199bbcdb3feSAndreas Gohr            if ($target === '' || !isset($pages[$target])) {
200bbcdb3feSAndreas Gohr                $anchor->removeAttribute('data-dw2pdf-target');
201bbcdb3feSAndreas Gohr                $anchor->removeAttribute('data-dw2pdf-hash');
202bbcdb3feSAndreas Gohr                continue;
203bbcdb3feSAndreas Gohr            }
204bbcdb3feSAndreas Gohr
205bbcdb3feSAndreas Gohr            if (!isset($pageAnchors[$target])) {
206bbcdb3feSAndreas Gohr                $check = false;
207bbcdb3feSAndreas Gohr                $pageAnchors[$target] = sectionID($target, $check);
208bbcdb3feSAndreas Gohr            }
209bbcdb3feSAndreas Gohr
210bbcdb3feSAndreas Gohr            $hash = $anchor->getAttribute('data-dw2pdf-hash');
211bbcdb3feSAndreas Gohr            $anchor->setAttribute(
212bbcdb3feSAndreas Gohr                'href',
213bbcdb3feSAndreas Gohr                '#' . $pageAnchors[$target] . '__' . $hash
214bbcdb3feSAndreas Gohr            );
215bbcdb3feSAndreas Gohr
216bbcdb3feSAndreas Gohr            $anchor->removeAttribute('data-dw2pdf-target');
217bbcdb3feSAndreas Gohr            $anchor->removeAttribute('data-dw2pdf-hash');
218bbcdb3feSAndreas Gohr        }
219bbcdb3feSAndreas Gohr
220bbcdb3feSAndreas Gohr        $wrapper = $dom->getElementsByTagName('div')->item(0);
221bbcdb3feSAndreas Gohr        if (!$wrapper) {
222bbcdb3feSAndreas Gohr            return $html;
223bbcdb3feSAndreas Gohr        }
224bbcdb3feSAndreas Gohr
225bbcdb3feSAndreas Gohr        $result = '';
226bbcdb3feSAndreas Gohr        foreach ($wrapper->childNodes as $node) {
227bbcdb3feSAndreas Gohr            $result .= $dom->saveHTML($node);
228bbcdb3feSAndreas Gohr        }
229bbcdb3feSAndreas Gohr
230bbcdb3feSAndreas Gohr        return $result;
231bbcdb3feSAndreas Gohr    }
232bbcdb3feSAndreas Gohr
233293d84e6SAndreas Gohr    /**
234b883aeceSAndreas Gohr     * Write the Table of Contents
235b883aeceSAndreas Gohr     *
236b883aeceSAndreas Gohr     * For double-sided documents the ToC is always on an even number of pages, so that the
237b883aeceSAndreas Gohr     * following content is on the correct odd/even page.
238b883aeceSAndreas Gohr     * The first page of ToC starts always at an odd page, so an additional blank page might
239b883aeceSAndreas Gohr     * be included before.
240b883aeceSAndreas Gohr     * There is no page numbering at the pages of the ToC.
241b883aeceSAndreas Gohr     *
242b883aeceSAndreas Gohr     * @param string $header The header text for the ToC (localized))
243b883aeceSAndreas Gohr     * @return void
244b883aeceSAndreas Gohr     * @throws MpdfException
245b883aeceSAndreas Gohr     */
246b883aeceSAndreas Gohr    public function toc(string $header): void
247c5c184fdSAndreas Gohr    {
248b883aeceSAndreas Gohr        $this->mpdf->TOCpagebreakByArray([
249b883aeceSAndreas Gohr            'toc-preHTML' => '<h2>' . $header . '</h2>',
250b883aeceSAndreas Gohr            'toc-bookmarkText' => $header,
251b883aeceSAndreas Gohr            'links' => true,
252b883aeceSAndreas Gohr            'outdent' => '1em',
253b883aeceSAndreas Gohr            'pagenumstyle' => '1'
254b883aeceSAndreas Gohr        ]);
255b883aeceSAndreas Gohr
2561d334a8cSAndreas Gohr        $this->write('<tocpagebreak>', HTMLParserMode::HTML_BODY, false, false);
257c5c184fdSAndreas Gohr    }
258c5c184fdSAndreas Gohr
259c5c184fdSAndreas Gohr    /**
260c5c184fdSAndreas Gohr     * Insert a cover page
261c5c184fdSAndreas Gohr     *
262c5c184fdSAndreas Gohr     * Should be called once at the beginning of the PDF generation. Will do nothing if
263c5c184fdSAndreas Gohr     * no cover page is configured.
264c5c184fdSAndreas Gohr     *
265c5c184fdSAndreas Gohr     * @return void
266c5c184fdSAndreas Gohr     * @throws MpdfException
267c5c184fdSAndreas Gohr     */
268c5c184fdSAndreas Gohr    public function cover(): void
269c5c184fdSAndreas Gohr    {
270c5c184fdSAndreas Gohr        $html = $this->template->getHTML('cover');
271c5c184fdSAndreas Gohr        if (!$html) return;
272c5c184fdSAndreas Gohr
2737542e5b4SAndreas Gohr        $this->conditionalPageBreak();
2747542e5b4SAndreas Gohr        $this->applyHeaderFooters();
2751d334a8cSAndreas Gohr        $this->write($html, HTMLParserMode::HTML_BODY, false, false);
276c5c184fdSAndreas Gohr
277c5c184fdSAndreas Gohr        $this->breakAfterMe();
278c5c184fdSAndreas Gohr    }
279c5c184fdSAndreas Gohr
280c5c184fdSAndreas Gohr    /**
281c5c184fdSAndreas Gohr     * Insert a back page
282c5c184fdSAndreas Gohr     *
283c5c184fdSAndreas Gohr     * Should be called once at the end of the PDF generation. Will do nothing if
284c5c184fdSAndreas Gohr     * no back page is configured.
285c5c184fdSAndreas Gohr     *
286c5c184fdSAndreas Gohr     * @return void
287c5c184fdSAndreas Gohr     * @throws MpdfException
288c5c184fdSAndreas Gohr     */
289c5c184fdSAndreas Gohr    public function back(): void
290c5c184fdSAndreas Gohr    {
291c5c184fdSAndreas Gohr        $html = $this->template->getHTML('back');
292c5c184fdSAndreas Gohr        if (!$html) return;
293c5c184fdSAndreas Gohr
2945eabda9aSAndreas Gohr        $this->conditionalPageBreak();
2951d334a8cSAndreas Gohr        $this->write($html, HTMLParserMode::HTML_BODY, false, false);
296c5c184fdSAndreas Gohr    }
297c5c184fdSAndreas Gohr
298c5c184fdSAndreas Gohr    /**
299c5c184fdSAndreas Gohr     * Finalize the document
300c5c184fdSAndreas Gohr     *
301c5c184fdSAndreas Gohr     * @return void
302c5c184fdSAndreas Gohr     * @throws MpdfException
303c5c184fdSAndreas Gohr     */
304c5c184fdSAndreas Gohr    public function endDocument(): void
305c5c184fdSAndreas Gohr    {
306c5c184fdSAndreas Gohr        // adds the closing div and finalizes the document
3071d334a8cSAndreas Gohr        $this->write('</div>', HTMLParserMode::HTML_BODY, false, true);
308c5c184fdSAndreas Gohr    }
309c5c184fdSAndreas Gohr
310c5c184fdSAndreas Gohr    /**
311c5c184fdSAndreas Gohr     * Set new headers and footers
312c5c184fdSAndreas Gohr     *
313c5c184fdSAndreas Gohr     * This will call the appropriate mpdf methods to set headers and footers. It should be called
314c5c184fdSAndreas Gohr     * before each wiki page is added to the PDF.
315c5c184fdSAndreas Gohr     *
316c5c184fdSAndreas Gohr     * On first call on this instance it will set the headers/footers for the first page, afterwards
317c5c184fdSAndreas Gohr     * it will use the standard headers/footers.
318c5c184fdSAndreas Gohr     *
319c5c184fdSAndreas Gohr     * We always set even and odd headers/footers, though they may be identical.
320c5c184fdSAndreas Gohr     * @return void
321c5c184fdSAndreas Gohr     */
322c5c184fdSAndreas Gohr    protected function applyHeaderFooters(): void
323c5c184fdSAndreas Gohr    {
324c5c184fdSAndreas Gohr        if ($this->isFirstPage) {
325c5c184fdSAndreas Gohr            $header = $this->template->getHTML('header', 'first');
326c5c184fdSAndreas Gohr            $footer = $this->template->getHTML('footer', 'first');
327c5c184fdSAndreas Gohr
328c5c184fdSAndreas Gohr            if ($header) {
329c5c184fdSAndreas Gohr                $this->mpdf->SetHTMLHeader($header, 'O');
330c5c184fdSAndreas Gohr                $this->mpdf->SetHTMLHeader($header, 'E');
331c5c184fdSAndreas Gohr            }
332c5c184fdSAndreas Gohr            if ($footer) {
333c5c184fdSAndreas Gohr                $this->mpdf->SetHTMLFooter($footer, 'O');
334c5c184fdSAndreas Gohr                $this->mpdf->SetHTMLFooter($footer, 'E');
335c5c184fdSAndreas Gohr            }
336c5c184fdSAndreas Gohr            $this->isFirstPage = false;
337c5c184fdSAndreas Gohr        } else {
338c5c184fdSAndreas Gohr            $headerOdd = $this->template->getHTML('header', 'odd');
339c5c184fdSAndreas Gohr            $headerEven = $this->template->getHTML('header', 'even');
340c5c184fdSAndreas Gohr            $footerOdd = $this->template->getHTML('footer', 'odd');
341c5c184fdSAndreas Gohr            $footerEven = $this->template->getHTML('footer', 'even');
342c5c184fdSAndreas Gohr
343c5c184fdSAndreas Gohr            if ($headerOdd) {
344c5c184fdSAndreas Gohr                $this->mpdf->SetHTMLHeader($headerOdd, 'O');
345c5c184fdSAndreas Gohr            }
346c5c184fdSAndreas Gohr            if ($headerEven) {
347c5c184fdSAndreas Gohr                $this->mpdf->SetHTMLHeader($headerEven, 'E');
348c5c184fdSAndreas Gohr            }
349c5c184fdSAndreas Gohr            if ($footerOdd) {
350c5c184fdSAndreas Gohr                $this->mpdf->SetHTMLFooter($footerOdd, 'O');
351c5c184fdSAndreas Gohr            }
352c5c184fdSAndreas Gohr            if ($footerEven) {
353c5c184fdSAndreas Gohr                $this->mpdf->SetHTMLFooter($footerEven, 'E');
354c5c184fdSAndreas Gohr            }
355c5c184fdSAndreas Gohr        }
356c5c184fdSAndreas Gohr    }
357c5c184fdSAndreas Gohr
358c5c184fdSAndreas Gohr    /**
359c5c184fdSAndreas Gohr     * Insert a page break if there was previous content
360c5c184fdSAndreas Gohr     *
361c5c184fdSAndreas Gohr     * @return void
3621d334a8cSAndreas Gohr     * @throws MpdfException
363c5c184fdSAndreas Gohr     */
364c5c184fdSAndreas Gohr    protected function conditionalPageBreak(): void
365c5c184fdSAndreas Gohr    {
366c5c184fdSAndreas Gohr        if ($this->breakBeforeNext) {
367c5c184fdSAndreas Gohr            $this->pageBreak();
368c5c184fdSAndreas Gohr            $this->breakBeforeNext = false;
369c5c184fdSAndreas Gohr        }
370c5c184fdSAndreas Gohr    }
371c5c184fdSAndreas Gohr
372c5c184fdSAndreas Gohr    /**
373c5c184fdSAndreas Gohr     * Signal that a page break should be inserted before the next content
374c5c184fdSAndreas Gohr     *
375c5c184fdSAndreas Gohr     * @return void
376c5c184fdSAndreas Gohr     */
377c5c184fdSAndreas Gohr    protected function breakAfterMe(): void
378c5c184fdSAndreas Gohr    {
379c5c184fdSAndreas Gohr        $this->breakBeforeNext = true;
380c5c184fdSAndreas Gohr    }
3811d334a8cSAndreas Gohr
3821d334a8cSAndreas Gohr    /**
3831d334a8cSAndreas Gohr     * Return the debug HTML collected so far
3841d334a8cSAndreas Gohr     *
3851d334a8cSAndreas Gohr     * Will return an empty string if debugging is not enabled.
3861d334a8cSAndreas Gohr     *
3871d334a8cSAndreas Gohr     * @return string The collected debug HTML
3881d334a8cSAndreas Gohr     */
3891d334a8cSAndreas Gohr    public function getDebugHTML(): string
3901d334a8cSAndreas Gohr    {
3911d334a8cSAndreas Gohr        return $this->debugHTML;
3921d334a8cSAndreas Gohr    }
3931d334a8cSAndreas Gohr
3941d334a8cSAndreas Gohr    /**
395f2000117SAndreas Gohr     * Persist the generated PDF to the provided destination file.
396f2000117SAndreas Gohr     *
397f2000117SAndreas Gohr     * @param string $cacheFile Absolute file path that should receive the PDF output
398f2000117SAndreas Gohr     * @return void
399f2000117SAndreas Gohr     * @throws MpdfException
400f2000117SAndreas Gohr     */
401f2000117SAndreas Gohr    public function outputToFile(string $cacheFile): void
402f2000117SAndreas Gohr    {
403f2000117SAndreas Gohr        $this->mpdf->Output($cacheFile, 'F');
404f2000117SAndreas Gohr    }
405f2000117SAndreas Gohr
406f2000117SAndreas Gohr    /**
4071d334a8cSAndreas Gohr     * A wrapper around MPDF::WriteHTML
4081d334a8cSAndreas Gohr     *
4091d334a8cSAndreas Gohr     * When debugging is enabled, the output is written to a debug buffer instead of the PDF.
4101d334a8cSAndreas Gohr     *
4111d334a8cSAndreas Gohr     * @param string $html The HTML code to write
4121d334a8cSAndreas Gohr     * @param int $mode Use HTMLParserMode constants. Controls what parts of the $html code is parsed.
4131d334a8cSAndreas Gohr     * @param bool $init Clears and sets buffers to Top level block etc.
4141d334a8cSAndreas Gohr     * @param bool $close If false leaves buffers etc. in current state, so that it can continue a block etc.
4151d334a8cSAndreas Gohr     * @throws MpdfException
4161d334a8cSAndreas Gohr     */
4171d334a8cSAndreas Gohr    protected function write(
4181d334a8cSAndreas Gohr        string $html,
4191d334a8cSAndreas Gohr        int $mode = HTMLParserMode::DEFAULT_MODE,
4201d334a8cSAndreas Gohr        bool $init = true,
4211d334a8cSAndreas Gohr        bool $close = true
4225340eaffSAndreas Gohr    ) {
4231d334a8cSAndreas Gohr        if (!$this->debug) {
4241d334a8cSAndreas Gohr            try {
4251d334a8cSAndreas Gohr                $this->mpdf->WriteHTML($html, $mode, $init, $close);
4261d334a8cSAndreas Gohr            } catch (MpdfException $e) {
4271d334a8cSAndreas Gohr                ErrorHandler::logException($e); // ensure the issue is logged
4281d334a8cSAndreas Gohr                throw $e;
4291d334a8cSAndreas Gohr            }
4301d334a8cSAndreas Gohr            return;
4311d334a8cSAndreas Gohr        }
4321d334a8cSAndreas Gohr
4331d334a8cSAndreas Gohr        // when debugging, just store the HTML
4341d334a8cSAndreas Gohr        if ($mode === HTMLParserMode::HEADER_CSS) {
4351d334a8cSAndreas Gohr            $this->debugHTML .= "\n<style>\n" . $html . "\n</style>\n";
4361d334a8cSAndreas Gohr        } else {
4371d334a8cSAndreas Gohr            $this->debugHTML .= "\n" . $html . "\n";
4381d334a8cSAndreas Gohr        }
4391d334a8cSAndreas Gohr    }
440c5c184fdSAndreas Gohr}
441