*/ class renderer_plugin_ansi extends Doku_Renderer { public const ESC = "\x1b"; public const CSI = self::ESC."["; public const OSC = self::ESC."]"; public const ST = self::ESC."\\"; public $toc = []; protected $footnotes = []; protected $store = ''; protected $nSpan = 0; protected $separator = ''; protected $list = []; protected $listtype = ''; /** @inheritdoc */ function getFormat() { return 'ansi'; } /** @inheritdoc */ public function document_start() { global $ID; $this->doc = ''; $this->toc = array(); $this->footnotes = array(); $this->store = ''; $this->nSpan = 0; $this->separator = ''; $meta = array(); $meta['format']['ansi']['Content-Type'] = 'text/ansi; charset=utf-8'; p_set_metadata($ID, $meta); } /** @inheritdoc */ public function document_end() { if (count($this->footnotes) > 0) { $this->doc .= DOKU_LF; $this->hr(); $id = 0; foreach ($this->footnotes as $footnote) { $id++; // the number of the current footnote // check its not a placeholder that indicates actual footnote text is elsewhere if (substr($footnote, 0, 5) != "@@FNT") { $this->doc .= self::CSI."1m" . $id . ') ' . self::CSI."22m"; // get any other footnotes that use the same markup $alt = array_keys($this->footnotes, "@@FNT$id"); if (count($alt)) { foreach ($alt as $ref) { $this->doc .= self::CSI."1m" . ($ref + 1) . ') ' . self::CSI."22m"; } } $this->doc .= $footnote . DOKU_LF; } } } // Prepare the TOC global $conf; if ($this->info['toc'] && is_array($this->toc) && $conf['tocminheads'] && count($this->toc) >= $conf['tocminheads']) { global $TOC; $TOC = $this->toc; } // make sure there are no empty paragraphs //$this->doc = preg_replace('#' . DOKU_LF . '\s*' . DOKU_LF . '\s*' . DOKU_LF . '#', DOKU_LF . DOKU_LF, $this->doc); } /** @inheritdoc */ public function header($text, $level, $pos, $returnonly = false) { if($level <= 2) { $this->doc .= ($level == 1 ? self::CSI."7m" : "") . self::ESC."#3" . $text . DOKU_LF . self::ESC."#4" . $text . ($level == 1 ? self::CSI."27m" : "") . DOKU_LF . DOKU_LF; } else { $this->doc .= self::CSI."7m" . str_pad("", 5-$level) . $text . str_pad("", 5-$level) . self::CSI."27m" . DOKU_LF . DOKU_LF; } } /** @inheritdoc */ public function section_open($levels) { } /** @inheritdoc */ public function section_close() { $this->doc .= DOKU_LF; } /** @inheritdoc */ public function cdata($text) { $this->doc .= $text; } /** @inheritdoc */ public function p_open() { } /** @inheritdoc */ public function p_close() { $this->doc .= DOKU_LF . DOKU_LF; } /** @inheritdoc */ public function linebreak() { $this->doc .= DOKU_LF; } /** @inheritdoc */ public function hr() { $this->doc .= '╺━━━━━━━━━━━━━━╸' . DOKU_LF . DOKU_LF; } /** @inheritdoc */ public function strong_open() { $this->doc .= self::CSI . "1m"; } /** @inheritdoc */ public function strong_close() { $this->doc .= self::CSI . "22m"; } /** @inheritdoc */ public function emphasis_open() { $this->doc .= self::CSI . "3m"; } /** @inheritdoc */ public function emphasis_close() { $this->doc .= self::CSI . "23m"; } /** @inheritdoc */ public function underline_open() { $this->doc .= self::CSI . "4m"; } /** @inheritdoc */ public function underline_close() { $this->doc .= self::CSI . "24m"; } /** @inheritdoc */ public function monospace_open() { $this->doc .= self::CSI . "11m"; } /** @inheritdoc */ public function monospace_close() { $this->doc .= self::CSI . "10m"; } /** @inheritdoc */ public function deleted_open() { $this->doc .= self::CSI . "9m"; } /** @inheritdoc */ public function deleted_close() { $this->doc .= self::CSI . "29m"; } /** @inheritdoc */ public function superscript_open() { $this->doc .= self::CSI . "73m"; } /** @inheritdoc */ public function superscript_close() { $this->doc .= self::CSI . "75m"; } /** @inheritdoc */ public function subscript_open() { $this->doc .= self::CSI . "74m"; } /** @inheritdoc */ public function subscript_close() { $this->doc .= self::CSI . "75m"; } /** @inheritdoc */ public function footnote_open() { $this->store = $this->doc; $this->doc = ''; } /** @inheritdoc */ public function footnote_close() { // recover footnote into the stack and restore old content $footnote = $this->doc; $this->doc = $this->store; $this->store = ''; // check to see if this footnote has been seen before $i = array_search($footnote, $this->footnotes); if ($i === false) { // its a new footnote, add it to the $footnotes array $id = count($this->footnotes) + 1; $this->footnotes[] = $footnote; } else { // seen this one before, translate the index to an id and save a placeholder $i++; $id = count($this->footnotes) + 1; $this->footnotes[] = "@@FNT" . ($i); } // output the footnote reference and link $this->doc .= self::CSI."1m[" . $id . "]" . self::CSI."22m"; } /** @inheritdoc */ public function listu_open($classes = null) { $this->listtype = "u"; } /** @inheritdoc */ public function listu_close() { $this->doc .= DOKU_LF; } /** @inheritdoc */ public function listo_open($classes = null) { $this->listtype = "o"; $this->list[] = 0; } /** @inheritdoc */ public function listo_close() { $this->doc .= DOKU_LF; array_pop($this->list); } /** @inheritdoc */ public function listitem_open($level, $node = false) { if($this->listtype=="o") $this->list[count($this->list)-1]++; $this->doc .= self::CSI . "1m" . str_pad("", $level*2) . ($this->listtype == "u" ? "*" : end($this->list).".") . self::CSI . "22m"; } /** @inheritdoc */ public function listitem_close() { } /** @inheritdoc */ public function listcontent_open() { } /** @inheritdoc */ public function listcontent_close() { $this->doc .= DOKU_LF; } /** @inheritdoc */ public function quote_open() { $this->doc .= " ┇ "; } /** @inheritdoc */ public function quote_close() { $this->doc .= DOKU_LF; } /** @inheritdoc */ public function preformatted($text) { $this->_highlight('pre', $text); } /** @inheritdoc */ public function file($text, $language = null, $filename = null, $options = null) { $this->_highlight('file', $text, $language, $filename, $options); } /** @inheritdoc */ public function code($text, $language = null, $filename = null, $options = null) { $this->_highlight('code', $text, $language, $filename, $options); } public function _highlight($type, $text, $language = null, $filename = null, $options = null) { $types = ["pre" => "", "file" => "\xf0\x9f\x93\x84", "code" => "\xe2\x8c\xa8\xef\xb8\x8f"]; $txt = explode("\n", $text); $this->doc .= "┏━╸" . $types[$type] . ($language === null ? "" : " [" . $language . "]") . ($filename === null ? "" : " " . $filename) . DOKU_LF; foreach($txt as $line) $this->doc .= "┃ " . $line . DOKU_LF; $this->doc .= "┗━╸" . DOKU_LF; } /** @inheritdoc */ public function acronym($acronym) { if (array_key_exists($acronym, $this->acronyms)) { $title = $this->acronyms[$acronym]; $this->doc .= $acronym . ' (' . $title . ')'; } else { $this->doc .= $acronym; } } /** @inheritdoc */ public function smiley($smiley) { if (isset($this->smileys[$smiley])) { // TODO: sixels $this->doc .= $smiley; } else { $this->doc .= $smiley; } } /** @inheritdoc */ public function entity($entity) { if (array_key_exists($entity, $this->entities)) { $this->doc .= $this->entities[$entity]; } else { $this->doc .= $entity; } } /** @inheritdoc */ public function multiplyentity($x, $y) { $this->doc .= $x . '×' . $y; } /** @inheritdoc */ public function singlequoteopening() { global $lang; $this->doc .= $lang['singlequoteopening']; } /** @inheritdoc */ public function singlequoteclosing() { global $lang; $this->doc .= $lang['singlequoteclosing']; } /** @inheritdoc */ public function apostrophe() { global $lang; $this->doc .= $lang['apostrophe']; } /** @inheritdoc */ public function doublequoteopening() { global $lang; $this->doc .= $lang['doublequoteopening']; } /** @inheritdoc */ public function doublequoteclosing() { global $lang; $this->doc .= $lang['doublequoteclosing']; } /** @inheritdoc */ public function camelcaselink($link, $returnonly = false) { return $this->internallink($link, $link); } /** @inheritdoc */ public function locallink($hash, $name = null, $returnonly = false) { $name = $this->_getLinkTitle($name, $hash, $isImage); $this->doc .= $name; } /** @inheritdoc */ public function internallink($id, $name = null, $search = null, $returnonly = false, $linktype = 'content') { global $ID; $params = ''; $parts = explode('?', $id, 2); if (count($parts) === 2) { $id = $parts[0]; $params = $parts[1]; } if($id === '') $id = $ID; $default = $this->_simpleTitle($id); $resolver = new PageResolver($ID); $id = $resolver->resolveId($id, $this->date_at, true); $exists = page_exists($id, $this->date_at, false, true); $name = $this->_getLinkTitle($name, $default, $isImage, $id, $linktype); $link = self::CSI . ($exists ? "35" : "31") . ";4m" . $this->_formatLink($id, $this->_getFullLink($id), $name) . self::CSI."24;39m"; if($returnonly) return $link; else $this->doc .= $link; } /** @inheritdoc */ public function externallink($url, $name = null, $returnonly = false) { $name = $this->_getLinkTitle($name, $url, $isImage); $link = self::CSI."95;4m" . $this->_formatLink($url, $url, $name) . self::CSI."24;39m"; if($returnonly) return $link; else $this->doc .= $link; } /** @inheritdoc */ public function interwikilink($match, $name, $wikiName, $wikiUri, $returnonly = false) { $name = $this->_getLinkTitle($name, $wikiUri, $isImage); $exists = null; $uri = $this->_resolveInterWiki($wikiName, $wikiUri, $exists); $link = self::CSI.($exists ? "95" : "91").";4m" . $this->_formatLink($match, $uri, $name) . self::CSI."24;39m"; if($returnonly) return $link; else $this->doc .= $link; } /** @inheritdoc */ public function windowssharelink($url, $name = null, $returnonly = false) { $name = $this->_getLinkTitle($name, $url, $isImage); $uri = str_replace('\\', '/', $url); $uri = 'file://' . $uri; $link = self::CSI."95;4m" . $this->_formatLink($url, $uri, $name) . self::CSI."24;39m"; if($returnonly) return $link; else $this->doc .= $link; } /** @inheritdoc */ public function emaillink($address, $name = null, $returnonly = false) { $name = $this->_getLinkTitle($name, '', $isImage); $address = html_entity_decode(obfuscate($address), ENT_QUOTES, 'UTF-8'); if (empty($name)) { $name = $address; } $link = self::CSI."95;4m" . $this->_formatLink($address, "mailto:".$address, $name) . self::CSI."24;39m"; if($returnonly) return $link; else $this->doc .= $link; } /** @inheritdoc */ public function internalmedia($src, $title = null, $align = null, $width = null, $height = null, $cache = null, $linking = null, $return = false) { global $ID; if (strpos($src, '#') !== false) { [$src, $hash] = sexplode('#', $src, 2); } $src = (new MediaResolver($ID))->resolveId($src, $this->date_at, true); $exists = media_exists($src); $noLink = false; $render = $linking != 'linkonly'; $render = $render && $exists; $render = $render && class_exists("Imagick"); $render = $render && $this->getConf("render_sixel"); [$ext, $mime] = mimetype($src, false); if (str_starts_with($mime, 'image') && $render) { $url = mediaFN($src, $this->_getLastMediaRevisionAt($src)); $image = new Imagick($url); // todo: resize if($width || $height) $image->thumbnailImage($width, $height); $image->setImageFormat('sixel'); $ret = $image->getImageBlob(); } else { $url = ml($src, [ 'id' => $ID, 'cache' => $cache, 'rev' => $this->_getLastMediaRevisionAt($src) ], true ); if(is_array($title)) $title = $title['title']; if($title) $title = "\xF0\x9F\x96\xBC\xEF\xB8\x8F ".$title; else $title = "\xF0\x9F\x96\xBC\xEF\xB8\x8F"; $ret = self::CSI.($exists ? "35" : "31") . ";4m" .$this->_formatLink($src, $this->_getFullLink($url), $title) . self::CSI."24;39m"; } if($return) return $ret; else $this->doc .= $ret; } /** @inheritdoc */ public function externalmedia($src, $title = null, $align = null, $width = null, $height = null, $cache = null, $linking = null, $return = false) { if($title) $title = "\xF0\x9F\x96\xBC\xEF\xB8\x8F ".$title; else $title = "\xF0\x9F\x96\xBC\xEF\xB8\x8F"; $ret = self::CSI."95;4m" . $this->_formatLink($src, $src, $title) . self::CSI."24;39m"; if($return) return $ret; else $this->doc .= $ret; } public function _media($src, $title = null, $align = null, $width = null, $height = null, $cache = null, $linking = null, $return = false) { } /** @inheritdoc */ public function rss($url, $params) { $this->doc .= "{{rss>" . $url . "}}" . DOKU_LF; } /** @inheritdoc */ public function table_open($maxcols = null, $numrows = null, $pos = null, $classes = null) { } /** @inheritdoc */ public function table_close($pos = null) { $this->doc .= DOKU_LF; } /** @inheritdoc */ public function tablethead_open() { } /** @inheritdoc */ public function tablethead_close() { } /** @inheritdoc */ public function tabletfoot_open() { } /** @inheritdoc */ public function tabletfoot_close() { } /** @inheritdoc */ public function tabletbody_open() { } /** @inheritdoc */ public function tabletbody_close() { } /** @inheritdoc */ public function tablerow_open($classes = null) { $this->separator = ''; } /** @inheritdoc */ public function tablerow_close() { $this->doc .= DOKU_LF; } /** @inheritdoc */ public function tableheader_open($colspan = 1, $align = null, $rowspan = 1, $classes = null) { $this->tablecell_open($colspan, $align, $rowspan, $classes); } /** @inheritdoc */ public function tableheader_close() { $this->tablecell_close(); } /** @inheritdoc */ public function tablecell_open($colspan = 1, $align = null, $rowspan = 1, $classes = null) { $this->nSpan = $colspan; $this->doc .= $this->separator; $this->separator = ' | '; } /** @inheritdoc */ public function tablecell_close() { if ($this->nSpan > 0) { $this->doc .= str_repeat(',', $this->nSpan - 1); } $this->nSpan = 0; } /** @inheritdoc */ public function _getLinkTitle($title, $default, &$isImage, $id = null, $linktype = 'content') { $isImage = false; if (is_array($title)) { $isImage = true; if (!is_null($default) && ($default != $title['title'])) return $default . " " . $title['title']; else return $title['title']; } elseif (is_null($title) || trim($title) == '') { if (useHeading($linktype) && $id) { $heading = p_get_first_heading($id); if ($heading) { return $heading; } } return $default; } else { return $title; } } public function _getFullLink($id) { global $conf; $base = $conf['baseurl'].$conf['basedir']; if ($conf['useslash']) { $id = strtr($id, ':', '/'); } return $base . $id; } public function _formatLink($id, $url, $name) { return self::OSC."8;;" . $url . self::ST . $name . ($id == $name ? "" : " [" . $id . "]") . self::OSC."8;;" . self::ST; } protected function _getLastMediaRevisionAt($media_id) { if (!$this->date_at || media_isexternal($media_id)) return ''; $changelog = new MediaChangeLog($media_id); return $changelog->getLastRevisionAt($this->date_at); } }