dokuwiki-plugin-ansi/renderer.php

712 lines
16 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
use dokuwiki\ChangeLog\MediaChangeLog;
use dokuwiki\File\MediaResolver;
use dokuwiki\File\PageResolver;
/**
* Renderer for ANSI/VT100 output
*
* @author JP Savard <yuki@a39.ca>
*/
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);
}
}