*/ // must be run within Dokuwiki if (!defined('DOKU_INC')) die(); /** * Latexit plugin extends default renderer class in this file */ require_once DOKU_INC . 'inc/parser/renderer.php'; /** * includes additional plugin classes */ require_once DOKU_INC . 'lib/plugins/latexit/classes/Package.php'; require_once DOKU_INC . 'lib/plugins/latexit/classes/RowspanHandler.php'; require_once DOKU_INC . 'lib/plugins/latexit/classes/BibHandler.php'; require_once DOKU_INC . 'lib/plugins/latexit/classes/LabelHandler.php'; require_once DOKU_INC . 'lib/plugins/latexit/classes/RecursionHandler.php'; /** * includes default DokuWiki files containing functions used by latexit plugin */ require_once DOKU_INC . 'inc/parserutils.php'; require_once DOKU_INC . 'inc/pageutils.php'; require_once DOKU_INC . 'inc/pluginutils.php'; require_once DOKU_INC . 'inc/confutils.php'; /** * Main latexit class, specifies how will be latex rendered */ class renderer_plugin_latexit extends Doku_Renderer { /** * Singleton helper plugin to store data for multiple renderer instances * * @var helper_plugin_latexit */ protected $store; /** * Stores the information about last list level * @var int */ protected $last_level; /** * Is true when the renderer is in a list * @var boolean */ protected $list_opened; /** * Stores the information about the level of recursion. * It stores the depth of current recusively added file. * @var int */ protected $recursion_level; /** * Used in recursively inserted files, stores information about headers level. * @var int */ protected $headers_level; /** * Is TRUE when recursive inserting should be used. * @var bool */ protected $recursive; /** * Stores the information about the headers level increase in last recursive insertion. * @var int */ protected $last_level_increase; /** * Stores the information about the number of cells found in a table row. * @var int */ protected $cells_count; /** * Stores the information about the number a table cols. * @var int */ protected $table_cols; /** * Stores the last colspan in a table. * @var int */ protected $last_colspan; /** * Stores the last rowspan in a table. * @var int */ protected $last_rowspan; /** * Stores the last align of a cell in a table. * @var int */ protected $last_align; /** * Is TRUE when renderer is inside a table. * @var bool */ protected $in_table; /** * An instance of a RowspanHandler class. * @var RowspanHandler */ protected $rowspan_handler; /** * Is set on true if the document contains media. * @var boolean */ protected $media; /** * Stores the instance of BibHandler * @var BibHandler */ protected $bib_handler; /** * This handler makes all the header labels unique * @var LabelHandler */ protected $label_handler; /** * This handler prevents recursive inserting of subpages to be an unending loop. * @var RecursionHandler */ protected $recursion_handler; /** * @var bool */ protected $bibliography; /** * Constructor * * Initializes the storage helper */ public function __construct() { $this->_initializeStore(); } /** * Make available as LaTeX renderer */ public function canRender($format) { if ($format == 'latex') { return true; } return false; } /** * Return the rendering format of the renderer - latex */ public function getFormat() { return 'latex'; } /** * Renderer is always created as a new instance. * It is required for recursive export. */ public function isSingleton() { return false; } /** * Allow overwriting options from within the document * * @param string $setting * @param bool $notset * @return mixed */ function getConf($setting, $notset = false) { global $ID; $opts = p_get_metadata($ID, 'plugin_latexit'); if($opts && isset($opts[$setting])) return $opts[$setting]; return parent::getConf($setting, $notset); } /** * function is called, when a document is started to being rendered. * It inicializes variables, adds headers to the LaTeX document and * sets the browser headers of the exported file. */ function document_start() { //register global variables used for recursive rendering global $latexit_level; global $latexit_headers; global $zip; //ID stores the current page id with namespaces, required for recursion prevention global $ID; if(is_null($this->store)) { $this->_initializeStore(); echo "aaa"; } //initialize variables $this->list_opened = FALSE; $this->recursive = FALSE; $this->in_table = FALSE; $this->last_level_increase = 0; $this->rowspan_handler = new RowspanHandler(); $this->media = FALSE; $this->bibliography = FALSE; if (!plugin_isdisabled('zotero')) { $this->bib_handler = BibHandler::getInstance(); } else { $this->bib_handler = NULL; } $this->label_handler = LabelHandler::getInstance(); $this->recursion_handler = RecursionHandler::getInstance(); //is this recursive export calling on a subpage? if (!isset($latexit_level) || is_null($latexit_level)) { $this->recursion_level = 0; } else { $this->recursion_level = $latexit_level; } if (!isset($latexit_headers) || is_null($latexit_headers)) { $this->headers_level = 0; } else { $this->headers_level = $latexit_headers; } //export of the main document if (!$this->_immersed()) { //the parent documented cannot be recursively inserted somewhere $this->recursion_handler->insert(wikifn($ID)); //prepare ZIP archive (will not be created, if it isn't necessary) $zip = new ZipArchive(); $this->_prepareZIP(); // configure language $document_lang = $this->getConf('document_lang'); $pckg = new Package('babel'); $pckg->addParameter($document_lang); $this->store->addPackage($pckg); // encoding is always UTF-8 $pckg = new Package('inputenc'); $pckg->addParameter('utf8x'); $this->store->addPackage($pckg); // add metadata to preamble $this->store->addPreamble(array('date', '\today')); // FIXME use the document's date instead $this->store->addPreamble(array('title', $this->getConf('title'))); $this->store->addPreamble(array('author', $this->getConf('author'))); // start document $this->_c('begin', 'document', 2); //if title or author or date is set, it prints it if ($this->getConf('date') || $this->getConf('title') != "" || $this->getConf('author') != "") { $this->_c('maketitle'); } //if table of contents should be displayed, it prints it if ($this->getConf('table_of_content')) { $this->_c('tableofcontents', NULL, 2); } } } /** * Prefix the created document with the pramble and packages */ protected function document_prefix() { // copy current doc and reset $doc = $this->doc; $this->doc = ''; //get document settings $params = array( $this->getConf('paper_size'), $this->getConf('output_format'), $this->getConf('font_size') . 'pt',); if ($this->getConf('landscape')) { $params[] = 'landscape'; } if ($this->getConf('draft')) { $params[] = 'draft'; } // print document settings $this->_c('documentclass', $this->getConf('document_class'), 1, $params); // print the packages $packages = $this->store->getPackages(); foreach ($packages as $package) { /** @var Package $package */ $this->doc .= $package->printUsePackage(); } // print the preamble $preamble = $this->store->getPreamble(); foreach($preamble as $command) { if(is_array($command)) { $this->_c($command[0], $command[1], $command[2], $command[3]); }else { $this->doc .= $command; } } // add custom document header $this->doc .= $this->getConf('document_header'); // finally readd the previously created document $this->doc .= $doc; } /** * function is called, when a document ends its rendering to finish the document * It finalizes the document. * */ function document_end() { /** @var ZipArchive $zip */ global $zip; //if a media were inserted in a recursively added file, we have to push this information up $this->_checkMedia(); //this is MAIN PAGE of exported file, we can finalize document if (!$this->_immersed()) { $this->_n(2); if ($this->_useBibliography() && !$this->bib_handler->isEmpty()) { $this->_c('bibliographystyle', $this->getConf('bibliography_style')); $this->_c('bibliography', $this->getConf('bibliography_name'), 2); } $this->doc .= $this->getConf('document_footer'); $this->_c('end', 'document'); // the document is done, add the prefix $this->document_prefix(); $this->_deleteMediaSyntax(); //finalize rendering of few entities $this->_highlightFixme(); $this->_removeEntities(); $this->_fixImageRef(); $output = "output" . time() . ".latex"; //file to download will be ZIP archive if ($this->media || ($this->_useBibliography() && !$this->bib_handler->isEmpty())) { $filename = $zip->filename; if ($this->_useBibliography() && !$this->bib_handler->isEmpty()) { $zip->addFromString($this->getConf('bibliography_name') . '.bib', $this->bib_handler->getBibtex()); } $zip->addFromString($output, $this->doc); //zip archive is created when this function is called, //so if no ZIP is needed, nothing is created $zip->close(); header("Content-type: application/zip"); header("Content-Disposition: attachment; filename=output" . time() . ".zip"); header("Content-length: " . filesize($filename)); header("Pragma: no-cache"); header("Expires: 0"); readfile($filename); //delete temporary zip file unlink($filename); } //file to download will be ordinary LaTeX file else { //set the headers, so the browsers knows, this is not the HTML file header('Content-Type: application/x-latex'); header("Content-Disposition: attachment; filename=$output;"); } } //this is RECURSIVELY added file else { //signal to the upper document, that we inserted media to ZIP archive if ($this->media) { $this->doc .= '%///MEDIA///'; } } } /** * Function is called, when renderer finds a new header. * It calls the LaTeX command for an appropriate level. * @param string $text Text of the header * @param int $level Level of the header. * @param int $pos Not used in LaTeX */ function header($text, $level, $pos) { //package hyperref will enable PDF bookmarks $package = new Package('hyperref'); $package->addParameter('unicode'); $this->store->addPackage($package); //set the types of headers to be used depending on configuration $levels = array(); if ($this->getConf('header_title')) { $levels[] = 'title'; } if ($this->getConf('header_part')) { $levels[] = 'part'; } if ($this->getConf('header_chapter') && $this->getConf('document_class') != 'article') { $levels[] = 'chapter'; } array_push($levels, 'section', 'subsection', 'subsubsection', 'paragraph', 'subparagraph'); if ($this->_immersed()) { //when document is recursively inserted, it will continue from previous headers level $level += $this->headers_level; } $this->_n(2); //the array of levels is indexed from 0 $level--; //such a level exists in the array if (isset($levels[$level])) { $this->_header($levels[$level], $text); } //level not in array, use default else { //to force a newline in latex, there has to be some empty char before, e.g. ~ $this->doc .= '~'; $this->_c('newline'); $this->_c('textbf', $this->_latexSpecialChars($text)); } //add a label, so each section can be referenced $label = $this->label_handler->newLabel($this->_createLabel($text)); $this->_c('label', 'sec:' . $label); } /** * Basic funcion called, when a text not from DokuWiki syntax is read * It adds the data to the document, potentionally dangerous characters for * LaTeX are escaped or removed. * @param string $text Text to be inserted. */ function cdata($text) { $this->doc .= $this->_latexSpecialChars($text); } /** * Function is called, when renderer finds a new paragraph. * It makes new paragraph in LaTeX Document. */ function p_open() { $this->_n(2); } /** * Function is called, when renderer finds a linebreak. * It adds new line in LaTeX Document. */ function linebreak() { if ($this->in_table) { //in tables in LaTeX there is different syntax $this->doc .= "\\newline "; } else { $this->doc .= "\\\\"; } } /** * Function is called, when renderer finds a horizontal line. * It adds centered horizontal line in LaTeX Document. */ function hr() { $this->_n(2); $this->_c('begin', 'center'); $this->doc .= "\line(1,0){250}\n"; $this->_c('end', 'center', 2); } /** * function is called, when renderer finds a strong text * It calls command for strong text in LaTeX Document. */ function strong_open() { $this->_open('textbf'); } /** * function is called, when renderer finds the end of a strong text */ function strong_close() { $this->_close(); } /** * function is called, when renderer finds an emphasised text * It calls command for emphasised text in LaTeX Document. */ function emphasis_open() { $this->_open('emph'); } /** * function is called, when renderer finds the end of an emphasised text */ function emphasis_close() { $this->_close(); } /** * function is called, when renderer finds an underlined text * It calls command for underlined text in LaTeX Document. */ function underline_open() { $this->_open('underline'); } /** * function is called, when renderer finds the end of an underlined text */ function underline_close() { $this->_close(); } /** * function is called, when renderer finds a monospace text * (all letters have same width) * It calls command for monospace text in LaTeX Document. */ function monospace_open() { $this->_open('texttt'); } /** * function is called, when renderer finds the end of a monospace text * (all letters have same width) */ function monospace_close() { $this->_close(); } /** * function is called, when renderer finds a subscript * It adds needed package and calls command for subscript in LaTeX Document. */ function subscript_open() { $package = new Package('fixltx2e'); $this->store->addPackage($package); $this->_open('textsubscript'); } /** * function is called, when renderer finds the end of a subscript */ function subscript_close() { $this->_close(); } /** * function is called, when renderer finds a superscript * It adds needed package and calls command for superscript in LaTeX Document. */ function superscript_open() { $package = new Package('fixltx2e'); $this->store->addPackage($package); $this->_open('textsuperscript'); } /** * function is called, when renderer finds the end of a superscript */ function superscript_close() { $this->_close(); } /** * function is called, when renderer finds a deleted text * It adds needed package and calls command for deleted text in LaTeX Document. */ function deleted_open() { $package = new Package('ulem'); $package->addParameter('normalem'); $this->store->addPackage($package); $this->_open('sout'); } /** * function is called, when renderer finds the end of a deleted text */ function deleted_close() { $this->_close(); } /** * function is called, when renderer finds a footnote * It calls footnote command in LaTeX Document. */ function footnote_open() { $this->_open('footnote'); } /** * function is called, when renderer finds the end of a footnote */ function footnote_close() { $this->_close(); } /** * function is called, when renderer finds start of an unordered list * It calls command for an unordered list in latex, even with right indention */ function listu_open() { $this->_list_open("itemize"); } /** * function is called, when renderer finds the end of an unordered list * It calls command for the end of an unordered list in latex, even with right indention */ function listu_close() { $this->_list_close("itemize"); } /** * function is called, when renderer finds start of an ordered list * It calls command for an ordered list in latex, even with right indention */ function listo_open() { $this->_list_open("enumerate"); } /** * function is called, when renderer finds the end of an ordered list * It calls command for the end of an ordered list in latex, even with right indention */ function listo_close() { $this->_list_close("enumerate"); } /** * function is called, when renderer finds start of a list item * It calls command for a list item in latex, even with right indention * @param int $level Level of indention. */ function listitem_open($level) { $this->last_level = $level; $this->_indent_list(); $this->doc .= " "; $this->_c('item', NULL, 0); } /** * function is called, when renderer finds the end of a list item content * It adds newline to the latex file. */ function listcontent_close() { $this->_n(); } /** * Original text is not formatted by DW, so this function just inserts the text as it is. * It just escapes special characters. * @param string $text Unformatted text. */ function unformatted($text) { $this->doc .= $this->_latexSpecialChars($text); } /** * Inserts PHP code to the document. * @param string $text PHP code. */ function php($text) { $this->code($text, "PHP"); } /** * Inserts block of PHP code to the document. * @param string $text PHP code. */ function phpblock($text) { $this->code($text, "PHP"); } /** * Inserts HTML code to the document. * @param string $text HTML code. */ function html($text) { $this->code($text, "HTML"); } /** * Inserts block of HTML code to the document. * @param string $text HTML code. */ function htmlblock($text) { $this->code($text, "HTML"); } /** * Inserts preformatted text (with all whitespaces) * @param string $text Preformatted text. */ function preformatted($text) { $this->_n(); $this->_c('begin', 'verbatim'); $this->doc .= $text; $this->_n(); $this->_c('end', 'verbatim'); } /** * Opens the quote environment. */ function quote_open() { $this->_n(); $this->_c('begin', 'quote'); } /** * Closes the quote environment. */ function quote_close() { $this->_n(); $this->_c('end', 'quote'); } /** * File tag is almost the same like the code tag, but it enables to download * the code directly from DW. * Therefore we just add the filename to the top of code. * @param string $text The code itself. * @param string $lang Programming language. * @param string $file The code will be exported from DW as a file. */ function file($text, $lang = null, $file = null) { $this->code($text, $lang, $file); } /** * Function adds a block of programming language code to LaTeX file * using the listings package. * @param string $text The code itself. * @param string $lang Programming language. * @param string $file The code can be inserted to DokuWiki as a file. */ function code($text, $lang = null, $file = null) { $pckg = new Package('listings'); $this->store->addPackage($pckg); //start code block $this->_open('lstset'); $this->doc .= 'frame=single'; if (!is_null($lang)) { //if language name is specified, insert it to LaTeX $this->doc .= ', language='; $this->doc .= $this->_latexSpecialChars($lang); } //insert filename if (!is_null($file)) { $this->doc .= ', title='; $this->doc .= $this->_latexSpecialChars($file); } $this->_close(); $this->_n(); //open the code block $this->_c('begin', 'lstlisting'); //get rid of some non-standard characters $text = str_replace('”', '"', $text); $text = str_replace('–', '-', $text); $this->doc .= $text; //close the code block $this->_c('end', 'lstlisting', 2); } /** * This function is called when an acronym is found. It just inserts it as a classic text. * I decided not to implement the mouse over text, although it is possible, but * it does not work in all PDF browsers. * http://tex.stackexchange.com/questions/32314/is-there-an-easy-way-to-add-hover-text-to-all-incidents-of-math-mode-where-the-h * @param string $acronym The Acronym. */ function acronym($acronym) { $this->doc .= $this->_latexSpecialChars($acronym); } /** * This function is called when a smiley is found. * LaTeX does not support smileys, so they are inserted as a normal text. * FIXME and DELETEME are exceptions, they are highlited (in the end of exporting). * @param string $smiley Smiley chars. */ function smiley($smiley) { if ($smiley == 'FIXME' || $smiley == 'DELETEME') { $pckg = new Package('soul'); $this->store->addPackage($pckg); $this->doc .= $smiley; } else { $this->doc .= $this->_latexSpecialChars($smiley); } } /** * DocuWiki can represent some characters as they typograficaly correct entities. * Most of them exist in LaTeX as well, but some only in math mode. * @param string $entity An entity. */ function entity($entity) { //this text is removed after exporting //it is here to disallow double escaping of some math characters $this->doc .= '///ENTITYSTART///'; switch ($entity) { case '->': $this->doc .= '$\rightarrow$'; break; case '<-': $this->doc .= '$\leftarrow$'; break; case '<->': $this->doc .= '$\leftrightarrow$'; break; case '=>': $this->doc .= '$\Rightarrow$'; break; case '<=': $this->doc .= '$\Leftarrow$'; break; case '<=>': $this->doc .= '$\Leftrightarrow$'; break; case '(c)': $this->doc .= '\copyright '; break; case '(tm)': $this->doc .= '\texttrademark '; break; case '(r)': $this->doc .= '\textregistered '; break; default: $this->doc .= $this->_latexSpecialChars($entity); break; } $this->doc .= '///ENTITYEND///'; } /** * Inserts multiply entity (eg. 640x480) to LaTeX file. * @param int $x First number * @param int $y Second number */ function multiplyentity($x, $y) { $this->doc .= '///ENTITYSTART///'; $this->doc .= '$'; $this->doc .= $this->_latexSpecialChars($x); $this->doc .= ' \times '; $this->doc .= $this->_latexSpecialChars($y); $this->doc .= '$'; $this->doc .= '///ENTITYEND///'; } /** * Inserts single quote opening to LaTeX depending on set language. */ function singlequoteopening() { $this->doc .= '`'; } /** * Inserts single quote closing to LaTeX depending on set language. */ function singlequoteclosing() { $this->doc .= '\''; } /** * Inserts apostrophe to LaTeX depending on set language. */ function apostrophe() { $this->doc .= '\''; } /** * Inserts double quote opening to LaTeX depending on set language. * Support for only English and Czech is implemented. */ function doublequoteopening() { switch ($this->getConf('document_lang')) { /* This is bugging, DW parses it strangely... FIXME consultation * case 'czech': $this->_open('uv'); break; */ default : $this->doc .= ',,'; break; } } /** * Inserts double quote closing to LaTeX depending on set language. * Support for only English and Czech is implemented. */ function doublequoteclosing() { switch ($this->getConf('document_lang')) { /* This is bugging, DW parses it strangely... FIXME consultation case 'czech': $this->_close(); break; */ default : $this->doc .= '"'; break; } } /** * Function is called, when renderer finds a link written in text like CamelCase. * It just calls the common link function. * @param string $link Internal link to a wiki page. */ function camelcaselink($link) { $this->internallink($link, $link); } /** * This function handles the links on the page itself (#something at the end of URL) * It inserts reference to LaTeX document * @param string $hash Label of a section * @param string $name Text of the original link */ function locallink($hash, $name = NULL) { $this->_insertLinkPackages(); if (!is_null($name)) { $this->doc .= $this->_latexSpecialChars($name); } else { $this->doc .= $this->_latexSpecialChars($hash); } $this->doc .= ' ('; $this->_c('autoref', "sec:" . $hash, 0); $this->doc .= ')'; } /** * function is called, when renderer finds an internal link * It resolves the internal link (namespaces, URL) * Depending on the configuration (inside the document): * It handles link as an external and calls proper function in LaTeX depending on the title * It recursively adds the linked page to the exported LaTeX file * This feature is not in classic plugin configuration. * If you want to have a link recursively inserted, add ~~RECURSIVE~~ just before it. * The count of ~ means the same as = for headers. It will determine the * level of first header used in recursively inserted text. * @param string $link Internal link (can be without proper namespace) * @param string/array $title Title, can be null or array (if it is media) */ function internallink($link, $title = NULL) { //register globals global $ID; //in this global var DokuWiki stores the current page id with namespaces global $latexit_level; global $latexit_headers; //escape link title if (!is_array($title)) { $title = $this->_latexSpecialChars($title); } $link_original = $link; //get current namespace from current page $current_namespace = getNS($ID); //get the page ID with right namespaces //$exists stores information, if the page exists. resolve_pageid($current_namespace, $link, $exists); //if the page does not exist, just insert it as common text if (!$exists) { $this->doc .= $title; return; } $params = ''; $absoluteURL = true; //get the whole URL $url = wl($link, $params, $absoluteURL); $url = $this->_secureLink($url); if ($this->recursive) { //check if it can continue with recursive inserting of this page if ($this->recursion_handler->disallow(wikifn($link))) { $this->_n(2); //warn the user about unending recursion $this->doc .= "%!!! RECURSION LOOP HAS BEEN PREVENTED !!!"; $this->_n(2); } else { //insert this page to RecursionHandler $this->recursion_handler->insert(wikifn($link)); //the level of recursion is increasing $latexit_level = $this->recursion_level + 1; $latexit_headers = $this->headers_level; //start parsing linked page - call the latexit plugin again $data = p_cached_output(wikifn($link), 'latexit'); $this->_n(2); //insert comment to LaTeX $this->doc .= "%RECURSIVELY INSERTED FILE START"; $this->_n(2); //insert parsed data $this->doc .= $data; $this->_n(2); //insert comment to LaTeX $this->doc .= "%RECURSIVELY INSERTED FILE END"; $this->_n(2); //get headers level to previous level $this->headers_level -= $this->last_level_increase; //remove this page from RecursionHandler $this->recursion_handler->remove(wikifn($link)); } } //handle internal links as they were external else { $this->_insertLink($url, $title, "internal", $link_original); } $this->recursive = FALSE; } /** * function is called, when renderer finds an external link * It calls proper function in LaTeX depending on the title * @param string $link External link * @param string/array $title Title, can be null or array (if it is media) */ function externallink($link, $title = NULL) { if (!is_array($title)) { $title = $this->_latexSpecialChars($title); } $link = $this->_secureLink($link); $this->_insertLink($link, $title, "external"); } /** * InterWiki links lead to another wikis and they can be written in special syntax. * This resolves the link and inserts it as normal external link. * @param string $link Original link in DW syntax * @param string $title Title of link, can also be image * @param string $wikiName Name of wiki (according to configuration) * @param string $wikiUri Text in link after wiki address */ function interwikilink($link, $title = NULL, $wikiName, $wikiUri) { $url = $this->_resolveInterWiki($wikiName, $wikiUri); if (is_null($title)) { $name = $wikiUri; } else { $name = $title; } $this->externallink($url, $name); } /** * Inserts a link to a file on local filesystem. * It just handles the link as an external link. * @param string $link Link to a file. * @param string $title Title of the link, can be image. */ function filelink($link, $title = NULL) { $this->externallink($link, $title); } /** * Inserts a link to a Windows share intranet server. * It just handles the link as an external link. * @param string $link Link to a file. * @param string $title Title of the link, can be image. */ function windowssharelink($link, $title = NULL) { $this->externallink($link, $title); } /** * function is called, when renderer finds an email link * It calls proper function in LaTeX depending on the name and sets mailto * @param string $address Email address * @param string/array $name Name, can be null or array (if it is media) */ function emaillink($address, $name = NULL) { if (!is_array($name)) { $name = $this->_latexSpecialChars($name); } $this->_insertLink($address, $name, "email"); } /** * This function is called when an image is uploaded to DokuWiki and inserted to a page. * It adds desired commands to the LaTeX file and also downloads the image with LaTeX * file in the ZIP archive. * * @param string $src DokuWiki source of the media. * @param string|null $title Mouseover title of image, we dont use this param (use imagareference plugin for correct labeling) * @param string|null $align Align of the media. * @param int|null $width Width of the media. But DW uses pixels, LaTeX does not. Therefore we dont use it. * @param int|null $height Height of the media. But DW uses pixels, LaTeX does not. Therefore we dont use it. * @param string $cache We delete cache, so we don't use this param. * @param bool $linking Not used. */ function internalmedia($src, $title = NULL, $align = NULL, $width = NULL, $height = NULL, $cache = NULL, $linking = NULL) { /** @var ZipArchive $zip */ global $zip; $media_folder = $this->getConf('media_folder'); if (strpos($src,':') !== false){ //the namespace structure is kept in folder structure in ZIP archive $namespaces = explode(':', $src); $path = ''; for ($i = 1; $i < count($namespaces); $i++) { if ($i != 1) { $path .= "/"; } $path .= $namespaces[$i]; } }else{ $path = $src; } //find media on FS $location = mediaFN($src); $exists = file_exists($location); if ($exists) { //exported file will be ZIP archive $this->media = TRUE; //add media to ZIP archive $zip->addFile($location, $media_folder . "/" . $path); } $mime = mimetype($src); if (substr($mime[1], 0, 5) == "image") { $this->_insertImage($path, $align, $media_folder); } else { $this->_insertFile($path, $title, $media_folder); } } /** * This function is called when an image from the internet is inserted to a page. * It adds desired commands to the LaTeX file and also downloads the image with LaTeX * file in the ZIP archive. * * @param string $src URL source of the media. * @param string|null $title Mouseover title of image, we dont use this param (use imagareference plugin for correct labeling) * @param string $align Align of the media. * @param int|null $width Width of the media. But DW uses pixels, LaTeX does not. Therefore we dont use it. * @param int|null $height Height of the media. But DW uses pixels, LaTeX does not. Therefore we dont use it. * @param string|null $cache We delete cache, so we don't use this param. * @param bool|null $linking Not used. */ function externalmedia($src, $title = NULL, $align = NULL, $width = NULL, $height = NULL, $cache = NULL, $linking = NULL) { global $conf; /** @var ZipArchive $zip */ global $zip; $this->media = TRUE; $media_folder = $this->getConf('media_folder'); //get just the name of file without path $filename = basename($src); //download the file to the DokuWiki TEMP folder $location = $conf["tmpdir"] . "/" . $filename; file_put_contents($location, file_get_contents($src)); //add file to the ZIP archive $path = $media_folder . "/" . $filename; $zip->addFile($location, $path); $mime = mimetype($filename); if (substr($mime[1], 0, 5) == "image") { $this->_insertImage($filename, $align, $media_folder); } else { $this->_insertFile($filename, $title, $media_folder); } } /** * Function is called, when a renderer finds a start of an table. * It inserts needed packages and the header of the table. * @param int $maxcols Maximum of collumns in the table * @param int $numrows Number of rows in table (not required in LaTeX) * @param int $pos This parameter is not required by LaTeX. */ function table_open($maxcols = null, $numrows = null, $pos = null) { $this->table_cols = $maxcols; //set environment to tables $this->in_table = true; $pckg = new Package('longtable'); $this->store->addPackage($pckg); //print the header $this->_c('begin', 'longtable', 0); $this->doc .= "{|"; for ($i = 0; $i < $maxcols; $i++) { $this->doc .= $this->getConf('default_table_align') . "|"; } $this->_close(); $this->_n(); $this->_c('hline'); } /** * Function is called in the end of every table. * It prints the footer of the table. * @param int $pos Not required in LaTeX. */ function table_close($pos = null) { //close the table environment $this->in_table = false; //print the footer $this->_c('end', 'longtable', 2); } /** * Function is called at start of every row in a table. */ function tablerow_open() { //set the number of cells printed $this->cells_count = 0; } /** * Function is called at the end of every row in a table */ function tablerow_close() { //add syntax for end of a row $this->doc .= " \\\\ "; $this->_n(); //add line $this->_c('hline'); $this->doc .= " "; $this->_n(); } /** * Function is called when the header row is reached. * It just prints regular row in bold. * * @param int $colspan * @param string|null $align * @param int $rowspan */ function tableheader_open($colspan = 1, $align = NULL, $rowspan = 1) { $this->tablecell_open($colspan, $align, $rowspan); $this->_open('textbf'); } /** * Function is called at the end of the header row. */ function tableheader_close() { $this->_close(); $this->tablecell_close(); } /** * Function handling exporting of each cell in a table. * @param int $colspan Sets collspan of the cell. * @param string $align Sets align of the cell. * @param int $rowspan Sets rows[am of the cell. */ function tablecell_open($colspan = 1, $align = NULL, $rowspan = 1) { if (is_null($align)) { $align = $this->getConf('default_table_align'); } else { //in DW align is left, right, center, in LaTeX just first letter $align = substr($align, 0, 1); } //if anything is not standard, we will have to use different closing of a cell $this->last_colspan = $colspan; $this->last_rowspan = $rowspan; $this->last_align = $align; //RowspanHandler stores information about the number of cells to be rowspanned if ($this->rowspan_handler->getRowspan($this->cells_count) != 0) { $this->doc .= ' & '; $this->rowspan_handler->decreaseRowspan($this->cells_count); $this->cells_count++; } //colspan or not default align if ($colspan != 1 || $align != $this->getConf('default_table_align')) { $this->doc .= "\\multicolumn{" . $colspan . "}{|$align|}{"; } //start a new rowspan using RowspanHandler if ($rowspan != 1) { $pckg = new Package('multirow'); $this->store->addPackage($pckg); $this->rowspan_handler->insertRowspan($rowspan - 1, $this->cells_count); $this->doc .= "\\multirow{" . $rowspan . "}{*}{"; } } /** * Function is called at the end of every cell. */ function tablecell_close() { //colspan or align different from default has been set in this cell if ($this->last_colspan != 1 || $this->last_align != $this->getConf('default_table_align')) { $this->doc .= "}"; } //rowspan has been set in this cell if ($this->last_rowspan != 1) { $this->doc .= "}"; } //are there any cells left in this row? $this->cells_count += $this->last_colspan; if ($this->table_cols != $this->cells_count) { $this->doc .= " & "; } } /** * Syntax of almost every basic LaTeX command is always the same. * @param string $command The name of a LaTeX command. * @param array $params Array of parameters of the command * @param bool $brackets Tells if the brackets should be used. */ protected function _open($command, $params = NULL, $brackets = true) { $this->doc .= "\\" . $command; //if params are set, print them all if (!is_null($params)) { $this->doc .= '['; $i = 0; foreach ($params as $p) { if ($i++ > 0) { $this->doc .= ', '; } $this->doc .= $p; } $this->doc .= ']'; } if ($brackets) { $this->doc .= "{"; } } /** * Closing tag of a lot of LaTeX commands is always same and will be called * in almost every close function. */ protected function _close() { $this->doc .= '}'; } /** * Helper function for printing almost all regular commands in LaTeX. * It can also print newlines after command and it supports parameters. * @param string $command Name of the command. * @param string $text Text to insert into the brackets. * @param int $newlines How many newlines after the command to insert. * @param array $params Array of parameters to be inserted. */ protected function _c($command, $text = NULL, $newlines = 1, $params = NULL) { //if there is no text, there will be no brackets if (is_null($text)) { $brackets = false; } else { $brackets = true; } $this->_open($command, $params, $brackets); //if there is no text, there is nothing to be closed if (!is_null($text)) { $this->doc .= $text; $this->_close(); } $this->_n($newlines); } /** * Function inserting new lines in the LaTeX file. * @param int $cnt How many new lines to insert. */ protected function _n($cnt = 1) { for ($i = 0; $i < $cnt; $i++) { $this->doc .= "\n"; } } /** * Function checks, if there were media added in a subfile. */ protected function _checkMedia() { //check if (preg_match('#%///MEDIA///#si', $this->doc)) { $this->media = TRUE; } //and delete any traces $this->_deleteMediaSyntax(); } /** * Function removes %///MEDIA/// from document */ protected function _deleteMediaSyntax() { str_replace('%///MEDIA///', '', $this->doc); } /** * Function inserts package used for hyperlinks. */ protected function _insertLinkPackages() { $package = new Package('hyperref'); //fixes the encoding warning $package->addParameter('unicode'); $this->store->addPackage($package); } /** * Function used for exporting lists, they differ only by command. * @param string $command Proper LaTeX list command */ protected function _list_open($command) { $this->_n(); if ($this->list_opened) { for ($i = 1; $i < $this->last_level + 1; $i++) { //indention $this->doc .= ' '; } } else { $this->list_opened = TRUE; } $this->_indent_list(); $this->_c('begin', $command); } /** * Function used for exporting the end of lists, they differ only by command. * @param string $command Proper LaTeX list command */ protected function _list_close($command) { if ($this->last_level == 1) { $this->list_opened = FALSE; } $this->_indent_list(); $this->_c('end', $command); } /** * Indents the list according to the last seen level. */ protected function _indent_list() { for ($i = 1; $i < $this->last_level; $i++) { $this->doc .= ' '; } } /** * This function highlights fixme DW command. * This format is used in some DokuWiki instances. * FIXME insert into documentation * format is: FIXME[author](description of a thing to fix) * (this feature comes from CCM at FIT CVUT, for whom I write the plugin) */ protected function _highlightFixme() { $this->doc = str_replace('FIXME', '\hl{FIXME}', $this->doc); $this->doc = str_replace('DELETEME', '\hl{DELETEME}', $this->doc); $this->doc = preg_replace_callback('#{FIXME}\[(.*?)\]\((.*?)\)#si', array(&$this, '_highlightFixmeHandler'), $this->doc); } /** * Function handling parsing of the fix me DW command. * * @param array $matches of strings $matches strings from the regex * @return string regex result replacement */ protected function _highlightFixmeHandler($matches) { $matches[1] = $this->_stripDiacritics($matches[1]); $matches[2] = $this->_stripDiacritics($matches[2]); return '{FIXME[' . $matches[1] . '](' . $matches[2] . ')}'; } /** * Insert header to the LaTeX document with right level command. * @param string $level LaTeX command for header on right level. * @param string $text Text of the Header. */ protected function _header($level, $text) { $this->_open($level); //pdflatex can have problems with special chars while making bookmarks //this is the fix $this->_open('texorpdfstring'); $text = str_replace("\"", "", $text); $this->doc .= $this->_latexSpecialChars($text); $this->_close(); $this->doc .= '{'; $this->doc .= $this->_pdfString($text); $this->_close(); $this->_close(); $this->_n(); } /** * This function finds out, if the current renderer is immersed in recursion. * @return boolean Is immersed in recursion? */ protected function _immersed() { if ($this->recursion_level > 0) { return true; } return false; } /** * Escapes LaTeX special chars. * Entities are in the middle of special tags so eg. MathJax texts are not escaped, but entities are. * @param string $text Text to be escaped. * @return string Escaped text. */ public function _latexSpecialChars($text) { return helper_plugin_latexit::escape($text); } /** * Function replaces entities, which have not been replaced using _latexSpecialChars function */ protected function _removeEntities() { $this->doc = preg_replace('#///ENTITYSTART///(.*?)///ENTITYEND///#si', '$1', $this->doc); } /** * Functions fixes few problems which come from imagereference plugin. */ protected function _fixImageRef() { $this->doc = str_replace('[h!]{\centering}', '[!ht]{\centering}', $this->doc); $this->doc = str_replace('\\ref{', '\autoref{', $this->doc); } /** * Function sets, if the next link will be inserted to the file recursively. * @param bool $recursive Will next link be added recursively? */ public function _setRecursive($recursive) { $this->recursive = $recursive; } /** * Function increases header level of a given number. * @param int $level Size of the increase. */ public function _increaseLevel($level) { $this->last_level_increase = $level; $this->headers_level += $level; } /** * function replacing some characters in MathJax mode * @param string $data Parsed text. */ public function _mathMode($data) { $data = str_replace('<=>', '\Leftrightarrow', $data); $data = str_replace('<->', '\leftrightarrow', $data); $data = str_replace('->', '\rightarrow', $data); $data = str_replace('<-', '\leftarrow', $data); $data = str_replace('=>', '\Rightarrow', $data); $data = str_replace('<=', '\Leftarrow', $data); $data = str_replace('...', '\ldots', $data); $data = str_replace('−', '-', $data); $this->doc .= $data; } /** * Function creates label from a header name. * @param string $text A header name. * @return string Label */ protected function _createLabel($text) { $text = preg_replace('#///ENTITYSTART///(.*?)///ENTITYEND///#si', '$1', $text); $text = $this->_stripDiacritics($text); $text = strtolower($text); $text = str_replace(" ", "_", $text); $text = $this->_removeMathAndSymbols($text); return $text; } /** * Escapes some characters in the URL. * @param string $link The URL. * @return string Escaped URL. */ protected function _secureLink($link) { $link = str_replace("\\", "\\\\", $link); $link = str_replace("#", "\#", $link); $link = str_replace("%", "\%", $link); $link = str_replace("&", "\&", $link); return $link; } /** * Prepares the ZIP archive. * @global string $conf global dokuwiki configuration * @global ZipArchive $zip pointer to our zip archive */ protected function _prepareZIP() { global $conf; /** @var ZipArchive $zip */ global $zip; //generate filename $filename = $conf["tmpdir"] . "/output" . time() . ".zip"; //create ZIP archive if ($zip->open($filename, ZipArchive::CREATE) !== TRUE) { exit("LaTeXit was not able to open <$filename>, check access rights.\n"); } } /** * Function prints the image command into the LaTeX file. * @param string $path relative path of the image. * @param string $align image align * @param string $media_folder path to the media folder. */ protected function _insertImage($path, $align, $media_folder) { $pckg = new Package('graphicx'); $pckg->addCommand('\\graphicspath{{' . $media_folder . '/}}'); $this->store->addPackage($pckg); //http://stackoverflow.com/questions/2395882/how-to-remove-extension-from-string-only-real-extension $path = preg_replace("/\\.[^.\\s]{3,4}$/", "", $path); //print align command if (!is_null($align)) { switch ($align) { case "center": $this->_c('centering', NULL, 0); break; case "left": $this->_c('raggedleft', NULL, 0); break; case "right": $this->_c('raggedright', NULL, 0); break; default : break; } } //insert image with params from config. $this->_c('includegraphics', $path, 1, array($this->getConf('image_params'))); } /** * Inserts a link to media file other from an image. * @param string $path Relative path to the file. * @param string $title Title of the link. * @param string $media_folder Location of media folder. */ protected function _insertFile($path, $title, $media_folder) { $path = $media_folder . "/" . $path; $this->filelink($path, $title); } /** * General function for inserting links * @param string $url Link URL. * @param string $title Link title. * @param string $type Link type (internal/external/email) * @param string $link_original Original link (for internal links it is used as a title) */ protected function _insertLink($url, $title, $type, $link_original = NULL) { $this->_insertLinkPackages(); if ($type == "email") { $mailto = "mailto:"; } else { $mailto = ""; } //no title was specified if (is_null($title) || (!is_array($title) && trim($title) == '')) { //for internal links, original DW link is inserted as a title if ($type == "internal") { $this->doc .= '\\href{' . $mailto . $url . '}{' . $link_original . '}'; } //email links have to contain mailto and address is used as text elseif ($type == "email") { $this->doc .= '\\href{' . $mailto . $url . '}{' . $url . '}'; } //reqular external link inserts the whole URL else { $this->doc .= '\\url{' . $mailto . $url . '}'; } } else { //is title an image? if (is_array($title)) { $this->doc .= '\\href{' . $mailto . $url . '}{'; if ($title["type"] == "internalmedia") { $this->internalmedia($title["src"], $title["title"], $title["align"]); } else { $this->externalmedia($title["src"], $title["title"], $title["align"]); } $this->doc .= '}'; } else { $this->doc .= '\\href{' . $mailto . $url . '}{' . $title . '}'; } } } /** * Handle a new BibEntry * @param string $entry */ public function _bibEntry($entry) { if ($this->_useBibliography()) { $this->bib_handler->insert($entry); } } /** * Escape the text, so it can be used as an pdf string for headers * @param string $text * @return string */ protected function _pdfString($text) { $text = $this->_stripDiacritics($this->_latexSpecialChars($text)); $text = $this->_removeMathAndSymbols($text); return $text; } /** * Removes all math and symbols from the text. * @param string $text * @return string */ protected function _removeMathAndSymbols($text) { $text = preg_replace("#\$(.*)\$#", "", $text); //next regex comes from this site: //http://stackoverflow.com/questions/5199133/function-to-return-only-alpha-numeric-characters-from-string $text = preg_replace("/[^a-zA-Z0-9_ ]+/", "", $text); return $text; } public function _useBibliography() { if (is_null($this->bib_handler)) { return false; } else { return true; } } /** * Initializes store variable. */ protected function _initializeStore() { $this->store = $this->loadHelper('latexit', true); } /** * Function removing diacritcs from a text. * From http://cs.wikibooks.org/wiki/PHP_prakticky/Odstran%C4%9Bn%C3%AD_diakritiky * @param string $data Text with diacritics * @return string Text withou diacritics */ protected function _stripDiacritics($data) { $table = Array( 'ä' => 'a', 'Ä' => 'A', 'á' => 'a', 'Á' => 'A', 'à' => 'a', 'À' => 'A', 'ã' => 'a', 'Ã' => 'A', 'â' => 'a', 'Â' => 'A', 'č' => 'c', 'Č' => 'C', 'ć' => 'c', 'Ć' => 'C', 'ď' => 'd', 'Ď' => 'D', 'ě' => 'e', 'Ě' => 'E', 'é' => 'e', 'É' => 'E', 'ë' => 'e', 'Ë' => 'E', 'è' => 'e', 'È' => 'E', 'ê' => 'e', 'Ê' => 'E', 'í' => 'i', 'Í' => 'I', 'ï' => 'i', 'Ï' => 'I', 'ì' => 'i', 'Ì' => 'I', 'î' => 'i', 'Î' => 'I', 'ľ' => 'l', 'Ľ' => 'L', 'ĺ' => 'l', 'Ĺ' => 'L', 'ń' => 'n', 'Ń' => 'N', 'ň' => 'n', 'Ň' => 'N', 'ñ' => 'n', 'Ñ' => 'N', 'ó' => 'o', 'Ó' => 'O', 'ö' => 'o', 'Ö' => 'O', 'ô' => 'o', 'Ô' => 'O', 'ò' => 'o', 'Ò' => 'O', 'õ' => 'o', 'Õ' => 'O', 'ő' => 'o', 'Ő' => 'O', 'ř' => 'r', 'Ř' => 'R', 'ŕ' => 'r', 'Ŕ' => 'R', 'š' => 's', 'Š' => 'S', 'ś' => 's', 'Ś' => 'S', 'ť' => 't', 'Ť' => 'T', 'ú' => 'u', 'Ú' => 'U', 'ů' => 'u', 'Ů' => 'U', 'ü' => 'u', 'Ü' => 'U', 'ù' => 'u', 'Ù' => 'U', 'ũ' => 'u', 'Ũ' => 'U', 'û' => 'u', 'Û' => 'U', 'ý' => 'y', 'Ý' => 'Y', 'ž' => 'z', 'Ž' => 'Z', 'ź' => 'z', 'Ź' => 'Z' ); return strtr($data, $table); } }