1<?php
2
3// phpcs:disable: PSR1.Methods.CamelCapsMethodName.NotCamelCaps
4// phpcs:disable: PSR2.Methods.MethodDeclaration.Underscore
5
6/**
7 * DokuWiki Plugin dw2pdf (Renderer Component)
8 * Render xhtml suitable as input for mpdf library
9 *
10 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
11 * @author  Andreas Gohr <gohr@cosmocode.de>
12 */
13class renderer_plugin_dw2pdf extends Doku_Renderer_xhtml
14{
15    private $lastHeaderLevel = -1;
16    private $originalHeaderLevel = 0;
17    private $difference = 0;
18    private static $header_count = [];
19    private static $previous_level = 0;
20
21    /**
22     * Stores action instance
23     *
24     * @var action_plugin_dw2pdf
25     */
26    private $actioninstance;
27
28    /**
29     * load action plugin instance
30     */
31    public function __construct()
32    {
33        $this->actioninstance = plugin_load('action', 'dw2pdf');
34    }
35
36    public function document_start()
37    {
38        global $ID;
39
40        parent::document_start();
41
42        //ancher for rewritten links to included pages
43        $check = false;
44        $pid = sectionID($ID, $check);
45
46        $this->doc .= "<a name=\"{$pid}__\">";
47        $this->doc .= "</a>";
48
49        self::$header_count[1] = $this->actioninstance->getCurrentBookChapter();
50    }
51
52    /**
53     * Make available as XHTML replacement renderer
54     *
55     * @param $format
56     * @return bool
57     */
58    public function canRender($format)
59    {
60        if ($format == 'xhtml') {
61            return true;
62        }
63        return false;
64    }
65
66    /**
67     * Simplified header printing with PDF bookmarks
68     *
69     * @param string $text
70     * @param int $level from 1 (highest) to 6 (lowest)
71     * @param int $pos
72     */
73    public function header($text, $level, $pos, $returnonly = false)
74    {
75        //skip empty headlines
76        if (!$text) {
77            return;
78        }
79        global $ID;
80
81        $hid = $this->_headerToLink($text, true);
82
83        //only add items within global configured levels (doesn't check the pdf toc settings)
84        $this->toc_additem($hid, $text, $level);
85
86        $check = false;
87        $pid = sectionID($ID, $check);
88        $hid = $pid . '__' . $hid;
89
90
91        // retrieve numbered headings option
92        $isnumberedheadings = $this->actioninstance->getExportConfig('headernumber');
93
94        $header_prefix = "";
95        if ($isnumberedheadings) {
96            if ($level > 0) {
97                if (self::$previous_level > $level) {
98                    for ($i = $level + 1; $i <= self::$previous_level; $i++) {
99                        self::$header_count[$i] = 0;
100                    }
101                }
102            }
103            self::$header_count[$level]++;
104
105            // $header_prefix = "";
106            for ($i = 1; $i <= $level; $i++) {
107                $header_prefix .= self::$header_count[$i] . ".";
108            }
109        }
110
111        // add PDF bookmark
112        $bookmark = '';
113        $maxbookmarklevel = $this->actioninstance->getExportConfig('maxbookmarks');
114        // 0: off, 1-6: show down to this level
115        if ($maxbookmarklevel && $maxbookmarklevel >= $level) {
116            $bookmarklevel = $this->calculateBookmarklevel($level);
117            $bookmark = sprintf(
118                '<bookmark content="%s %s" level="%d" />',
119                $header_prefix,
120                $this->_xmlEntities($text),
121                $bookmarklevel
122            );
123        }
124
125        // print header
126        $this->doc .= DOKU_LF . "<h$level>$bookmark";
127        $this->doc .= $header_prefix . "<a name=\"$hid\">";
128        $this->doc .= $this->_xmlEntities($text);
129        $this->doc .= "</a>";
130        $this->doc .= "</h$level>" . DOKU_LF;
131        self::$previous_level = $level;
132    }
133
134    /**
135     * Bookmark levels might increase maximal +1 per level.
136     * (note: levels start at 1, bookmarklevels at 0)
137     *
138     * @param int $level 1 (highest) to 6 (lowest)
139     * @return int
140     */
141    protected function calculateBookmarklevel($level)
142    {
143        if ($this->lastHeaderLevel == -1) {
144            $this->lastHeaderLevel = $level;
145        }
146        $step = $level - $this->lastHeaderLevel;
147        if ($step > 1) {
148            $this->difference += $step - 1;
149        }
150        if ($step < 0) {
151            $this->difference = min($this->difference, $level - $this->originalHeaderLevel);
152            $this->difference = max($this->difference, 0);
153        }
154
155        $bookmarklevel = $level - $this->difference;
156
157        if ($step > 1) {
158            $this->originalHeaderLevel = $bookmarklevel;
159        }
160
161        $this->lastHeaderLevel = $level;
162        return $bookmarklevel - 1; //zero indexed
163    }
164
165    /**
166     * Render a page local link
167     *
168     * // modified copy of parent function
169     *
170     * @param string $hash hash link identifier
171     * @param string $name name for the link
172     * @param bool $returnonly
173     * @return string|void
174     *
175     * @see Doku_Renderer_xhtml::locallink
176     */
177    public function locallink($hash, $name = null, $returnonly = false)
178    {
179        global $ID;
180        $name = $this->_getLinkTitle($name, $hash, $isImage);
181        $hash = $this->_headerToLink($hash);
182        $title = $ID . ' ↵';
183
184        $check = false;
185        $pid = sectionID($ID, $check);
186
187        $this->doc .= '<a href="#' . $pid . '__' . $hash . '" title="' . $title . '" class="wikilink1">';
188        $this->doc .= $name;
189        $this->doc .= '</a>';
190    }
191
192    /**
193     * Wrap centered media in a div to center it
194     *
195     * @param string $src media ID
196     * @param string $title descriptive text
197     * @param string $align left|center|right
198     * @param int $width width of media in pixel
199     * @param int $height height of media in pixel
200     * @param string $cache cache|recache|nocache
201     * @param bool $render should the media be embedded inline or just linked
202     * @return string
203     */
204    public function _media(
205        $src,
206        $title = null,
207        $align = null,
208        $width = null,
209        $height = null,
210        $cache = null,
211        $render = true
212    ) {
213
214        $out = '';
215        if ($align == 'center') {
216            $out .= '<div align="center" style="text-align: center">';
217        }
218
219        $out .= parent::_media($src, $title, $align, $width, $height, $cache, $render);
220
221        if ($align == 'center') {
222            $out .= '</div>';
223        }
224
225        return $out;
226    }
227
228    /**
229     * hover info makes no sense in PDFs, so drop acronyms
230     *
231     * @param string $acronym
232     */
233    public function acronym($acronym)
234    {
235        $this->doc .= $this->_xmlEntities($acronym);
236    }
237
238    /**
239     * reformat links if needed
240     *
241     * @param array $link
242     * @return string
243     */
244    public function _formatLink($link)
245    {
246
247        // for internal links contains the title the pageid
248        if (in_array($link['title'], $this->actioninstance->getExportedPages())) {
249            [/* url */, $hash] = sexplode('#', $link['url'], 2, '');
250
251            $check = false;
252            $pid = sectionID($link['title'], $check);
253            $link['url'] = "#" . $pid . '__' . $hash;
254        }
255
256        // prefix interwiki links with interwiki icon
257        if ($link['name'][0] != '<' && preg_match('/\binterwiki iw_(.\w+)\b/', $link['class'], $m)) {
258            if (file_exists(DOKU_INC . 'lib/images/interwiki/' . $m[1] . '.png')) {
259                $img = DOKU_BASE . 'lib/images/interwiki/' . $m[1] . '.png';
260            } elseif (file_exists(DOKU_INC . 'lib/images/interwiki/' . $m[1] . '.gif')) {
261                $img = DOKU_BASE . 'lib/images/interwiki/' . $m[1] . '.gif';
262            } else {
263                $img = DOKU_BASE . 'lib/images/interwiki.png';
264            }
265
266            $link['name'] = sprintf(
267                '<img src="%s" width="16" height="16" style="vertical-align: middle" class="%s" />%s',
268                $img,
269                $link['class'],
270                $link['name']
271            );
272        }
273        return parent::_formatLink($link);
274    }
275
276    /**
277     * no obfuscation for email addresses
278     *
279     * @param string $address
280     * @param null $name
281     * @param bool $returnonly
282     * @return string|void
283     */
284    public function emaillink($address, $name = null, $returnonly = false)
285    {
286        global $conf;
287        $old = $conf['mailguard'];
288        $conf['mailguard'] = 'none';
289        parent::emaillink($address, $name, $returnonly);
290        $conf['mailguard'] = $old;
291    }
292}
293