<?php
/**
 * ODT Plugin: Exports to ODT
 *
 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
 * @author Andreas Gohr <andi@splitbrain.org>
 * @author Aurelien Bompard <aurelien@bompard.org>
 */
// must be run within Dokuwiki
if(!defined('DOKU_INC')) die();

require_once DOKU_PLUGIN . 'odt/helper/cssimport.php';
require_once DOKU_PLUGIN . 'odt/ODT/ODTDefaultStyles.php';

// Central class for ODT export
require_once DOKU_PLUGIN . 'odt/ODT/ODTDocument.php';

/**
 * The Page Renderer
 * 
 * @package DokuWiki\Renderer\Page
 */
class renderer_plugin_odt_page extends Doku_Renderer {
    /** @var helper_plugin_odt_cssimport */
    protected $import = null;
    /** @var helper_plugin_odt_config */
    protected $config = null;
    protected $document = null;
    /** @var string */
    protected $css;
    /** @var bool */
    protected $init_ok;

    /**
     * Constructor. Loads helper plugins.
     */
    public function __construct() {
        // Set up empty array with known config parameters
        $this->config = plugin_load('helper', 'odt_config');

        // Create and initialize document
        $this->document = new ODTDocument();
        $this->init_ok = $this->document->initialize ();
    }

    /**
     * Set a config parameter from extern.
     */
    public function setConfigParam($name, $value) {
        $this->config->setParam($name, $value);
    }

    /**
     * Is the $string specified the name of a ODT plugin config parameter?
     *
     * @return bool Is it a config parameter?
     */
    public function isConfigParam($string) {
        return $this->config->isParam($string);
    }

    /**
     * Returns the format produced by this renderer.
     */
    function getFormat(){
        return "odt";
    }

    /**
     * Do not make multiple instances of this class
     */
    function isSingleton(){
        return true;
    }

    public function replaceURLPrefixesCallback ($property, $value, $url) {
        if (strncmp($url, '/lib/plugins', strlen('/lib/plugins')) == 0) {
            return DOKU_INC.substr($url,1);
        }
        return $url;
    }

    /**
     * Load and imports CSS.
     */
    protected function load_css() {
        global $conf, $lang;

        /** @var helper_plugin_odt_dwcssloader $loader */
        $loader = plugin_load('helper', 'odt_dwcssloader');
        if ( $loader != NULL ) {
            $this->css = $loader->load
                ('odt', 'odt', $this->config->getParam('css_template'));
        }

        // Import CSS (old API, deprecated)
        $this->import = plugin_load('helper', 'odt_cssimport');
        if ( $this->import != NULL ) {
            $this->import->importFromString ($this->css);

            // Call adjustLengthValues to make our callback function being called for every
            // length value imported. This gives us the chance to convert it once from
            // pixel to points.
            $this->import->adjustLengthValues (array($this, 'adjustLengthCallback'));
        }

        // Set CSS usage according to configuration
        switch ($this->config->getParam('css_usage')) {
            case 'basic style import':
                $this->document->setCSSUsage('basic');
                break;
            case 'full':
                $this->document->setCSSUsage('full');
                break;
            default:
                $this->document->setCSSUsage('off');
                break;
        }
        $this->document->setMediaSelector ($this->config->getParam('media_sel'));

        // Put some root element on the HTML stack which should always
        // be present for our CSS matching
        $this->document->addHTMLElement ('html', 'lang="'.$conf['lang'].'" dir="'.$lang['direction'].'"');
        $this->document->addHTMLElement ('body');
        $this->document->addHTMLElement ('div', 'id="dokuwiki__site"');
        $this->document->addHTMLElement ('div', 'id="dokuwiki__top" class="site dokuwiki mode_show tpl_adoradark loggedIn"');
        $this->document->addHTMLElement ('div', 'id="dokuwiki__content"');
        $this->document->addHTMLElement ('div', 'class="page group"');

        // Import CSS (new API)
        $this->document->importCSSFromString
            ($this->css, $this->config->getParam('media_sel'), array($this, 'replaceURLPrefixesCallback'), false, $this->config->getParam('olist_label_align'));
    }

    /**
     * Configure units to our configuration values.
     */
    protected function setupUnits()
    {
        $this->document->setPixelPerEm($this->config->getParam ('css_font_size'));
        $this->document->setTwipsPerPixelX($this->config->getParam ('twips_per_pixel_x'));
        $this->document->setTwipsPerPixelY($this->config->getParam ('twips_per_pixel_y'));
    }

    /**
     * Initialize the document,
     * Do the things that are common to all documents regardless of the
     * output format (ODT or PDF).
     */
    function document_setup()
    {
        global $ID;

        // First, get export mode.
        $warning = '';
        $mode = $this->config->load($warning);

        // Setup Units before CSS import!
        $this->setupUnits();

        switch($mode) {
            case 'ODT template':
            case 'CSS template':
                break;
            default:
                // Set ordered list alignment before calling load_css().
                // load_css() will eventually overwrite the list settings!
                $this->document->setOrderedListParams(NULL, $this->config->getParam('olist_label_align'));
                $this->document->setUnorderedListParams(NULL, $this->config->getParam('olist_label_align'));
                break;
        }

        // Import CSS files
        $this->load_css();

        switch($mode) {
            case 'ODT template':
                // Document based on ODT template.
                $this->buildODTPathes ($ODTtemplate, $temp_dir);
                $this->document->importODTStyles($ODTtemplate, $temp_dir);

                if ($this->config->getParam ('apply_fs_to_non_css')) {
                    $this->document->adjustFontSizes($this->config->getParam('css_font_size').'pt');
                }
                break;

            case 'CSS template':
                // Document based on DokuWiki CSS template.
                $media_sel = $this->config->getParam ('media_sel');
                $template = $this->config->getParam ('odt_template');
                $directory = $this->config->getParam ('tpl_dir');
                $template_path = $this->config->getParam('mediadir').'/'.$directory."/".$template;
                $this->document->importCSSFromFile
                    ($template_path, $media_sel, array($this, 'replaceURLPrefixesCallback'), $this->config->getParam('olist_label_align'));

                // Set outline style.
                $this->document->setOutlineStyle($this->config->getParam('outline_list_style'));
                break;

            default:
                // Document from scratch.

                // Set outline style.
                $this->document->setOutlineStyle($this->config->getParam('outline_list_style'));

                if ($this->config->getParam ('apply_fs_to_non_css')) {
                    $this->document->adjustFontSizes($this->config->getParam('css_font_size').'pt');
                }
                break;
        }

        // If we are using ODT for style import (a template or the default 'styles.xml')
        // then adjust the pixel per em value to the font-size of the default paragraph style
        // otherwise plugins might inherit a wrong font-size on CSS import!
        if ($mode != 'CSS template') {
            $default = $this->document->getDefaultStyle ('paragraph');
            if ($default != NULL) {
                $fontFize = $default->getProperty('font-size');
                if (!empty($fontFize)) {
                    $fontFizeInPx = $this->document->toPixel($fontFize);
                    if (!empty($fontFizeInPx)) {
                        $this->document->setPixelPerEm($fontFizeInPx);
                    }
                }
            }
        }

        // Setup page format.
        $this->document->setStartPageFormat ($this->config->getParam ('format'),
                                             $this->config->getParam ('orientation'),
                                             $this->config->getParam ('margin_top'),
                                             $this->config->getParam ('margin_right'),
                                             $this->config->getParam ('margin_bottom'),
                                             $this->config->getParam ('margin_left'));

        // Set title in meta info.
        // FIXME article title != book title  SOLUTION: overwrite at the end for book
        $this->document->setTitle($ID);

        // Enable/disable links according to configuration
        $disabled = $this->config->getParam ('disable_links');
        if ($disabled) {
            $this->document->disableLinks();
        } else {
            $this->document->enableLinks();
        }

        $this->set_page_bookmark($ID);
    }
    
    /**
     * Initialize the rendering
     */
    function document_start() {
        global $ID;

        if (!$this->init_ok) {
            // Initialization of the ODT document failed!
            // Send "Internal Server Error"
            http_status(500);
            $message = $this->getLang('init_failed_msg');
            $message = str_replace('%DWVERSION%', getVersion(), $message);
            $instructions = p_get_instructions($message);
            print p_render('xhtml', $instructions, $info);

            exit;
        }

        // Initialize the document
        $this->document_setup();

        // Create HTTP headers
        $output_filename = str_replace(':','-',$ID).'.odt';
        $headers = array(
            'Content-Type' => 'application/vnd.oasis.opendocument.text',
            'Content-Disposition' => 'attachment; filename="'.$output_filename.'";',
        );

        // store the content type headers in metadata
        p_set_metadata($ID,array('format' => array('odt_page' => $headers) ));
    }

    /**
     * Closes the document
     */
    function document_end(){
        // Build the document
        $this->finalize_ODTfile();

        // Refresh certain config parameters e.g. 'disable_links'
        $this->config->refresh();

        // Reset state.
        $this->document->state->reset();
    }

    /**
     * This function sets the page format.
     * The format, orientation and page margins can be changed.
     * See function queryFormat() in ODT/page.php for supported formats.
     *
     * @param string  $format         e.g. 'A4', 'A3'
     * @param string  $orientation    e.g. 'portrait' or 'landscape'
     * @param numeric $margin_top     Top-Margin in cm, default 2
     * @param numeric $margin_right   Right-Margin in cm, default 2
     * @param numeric $margin_bottom  Bottom-Margin in cm, default 2
     * @param numeric $margin_left    Left-Margin in cm, default 2
     * @see ODTDocument::setPageFormat
     */
    public function setPageFormat ($format=NULL, $orientation=NULL, $margin_top=NULL, $margin_right=NULL, $margin_bottom=NULL, $margin_left=NULL) {
        $this->document->setPageFormat ($format, $orientation, $margin_top, $margin_right, $margin_bottom, $margin_left);
    }

    /**
     * Completes the ODT file.
     */
    public function finalize_ODTfile() {
        global $ID;

        $this->buildODTPathes ($ODTtemplate, $temp_dir);

        // Build/assign the document
        $this->doc = $this->document->getODTFileAsString ($ODTtemplate, $temp_dir);
    }

    /**
     * Simple setter to enable creating links.
     */
    function enable_links() {
        $this->config->setParam ('disable_links', false);
        $this->document->enableLinks();
    }

    /**
     * Simple setter to disable creating links.
     */
    function disable_links() {
        $this->config->setParam ('disable_links', true);
        $this->document->disableLinks();
    }

    /**
     * Dummy function.
     *
     * @return string
     */
    function render_TOC() {
        return '';
    }

    /**
     * This function does not really render an index but inserts a placeholder.
     *
     * @return string
     * @see ODTDocument::insertIndex for API wrapper function
     * @see ODTIndex::insertIndex for more information
     */
    function render_index($type='toc', $settings=NULL) {
        $data = array();
        $data = $this->get_index_settings($type, $settings);
        $this->document->insertIndex($type, $data);
        return '';
    }

    /**
     * This function detmerines the settings for a TOC or chapter index.
     * The layout settings are taken from the configuration and $settings.
     * The result is returned as an array.
     *
     * $settings can include the following options syntax:
     * - Title e.g. 'title=Example;'.
     *   Default is 'Table of Contents' (for english, see language files for other languages default value).
     * - Leader sign, e.g. 'leader-sign=.;'.
     *   Default is '.'.
     * - Indents (in cm), e.g. 'indents=indents=0,0.5,1,1.5,2,2.5,3;'.
     *   Default is 0.5 cm indent more per level.
     * - Maximum outline/TOC level, e.g. 'maxtoclevel=5;'.
     *   Default is taken from DokuWiki config setting 'maxtoclevel'.
     * - Insert pagebreak after TOC, e.g. 'pagebreak=1;'.
     *   Default is '1', means insert pagebreak after TOC.
     * - Set style per outline/TOC level, e.g. 'styleL2="color:red;font-weight:900;";'.
     *   Default is 'color:black'.
     *
     * It is allowed to use defaults for all settings by omitting $settings.
     * Multiple settings can be combined, e.g. 'leader-sign=.;indents=0,0.5,1,1.5,2,2.5,3;'.
     */
    protected function get_index_settings($type, $settings) {
        $matches = array();
        $data = array();

        $data ['numbered_headings'] = false;
        if ($this->config->getParam('outline_list_style') == 'Numbers') {
            $data ['numbered_headings'] = true;
        }

        // It seems to be not supported in ODT to have a different start
        // outline level than 1.
        $data ['maxlevel'] = $this->config->getParam('toc_maxlevel');
        if ( preg_match('/maxlevel=[^;]+;/', $settings, $matches) === 1 ) {
            $temp = substr ($matches [0], 9);
            $temp = trim ($temp, ';');
            $data ['maxlevel'] = $temp;
        }

        // Determine title, default for table of contents is 'Table of Contents'.
        // Default for chapter index is empty.
        // Syntax for 'Test' as title would be "title=test;".
        $data ['title'] = '';
        if ($type == 'toc') {
            $data ['title'] = $this->getLang('toc_title');
        }
        if ( preg_match('/title=[^;]+;/', $settings, $matches) === 1 ) {
            $temp = substr ($matches [0], 6);
            $temp = trim ($temp, ';');
            $data ['title'] = $temp;
        }

        // Determine leader-sign, default is '.'.
        // Syntax for '.' as leader-sign would be "leader_sign=.;".
        $data ['leader_sign'] = $this->config->getParam('toc_leader_sign');
        if ( preg_match('/leader_sign=[^;]+;/', $settings, $matches) === 1 ) {
            $temp = substr ($matches [0], 12);
            $temp = trim ($temp, ';');
            $data ['leader_sign'] = $temp [0];
        }

        // Determine indents, default is '0.5' (cm) per level.
        // Syntax for a indent of '0.5' for 5 levels would be "indents=0,0.5,1,1.5,2;".
        // The values are absolute for each level, not relative to the higher level.
        $data ['indents'] = explode (',', $this->config->getParam('toc_indents'));
        if ( preg_match('/indents=[^;]+;/', $settings, $matches) === 1 ) {
            $temp = substr ($matches [0], 8);
            $temp = trim ($temp, ';');
            $data ['indents'] = explode (',', $temp);
        }

        // Determine pagebreak, default is on '1'.
        // Syntax for pagebreak off would be "pagebreak=0;".
        $data ['pagebreak'] = $this->config->getParam('toc_pagebreak');
        if ( preg_match('/pagebreak=[^;]+;/', $settings, $matches) === 1 ) {
            $temp = substr ($matches [0], 10);
            $temp = trim ($temp, ';');
            $data ['pagebreak'] = 'false';            
            if ( $temp == '1' ) {
                $data ['pagebreak'] = 'true';
            } else if ( strcasecmp($temp, 'true') == 0 ) {
                $data ['pagebreak'] = 'true';
            }
        }

        // Determine text style for the index heading.
        $data ['style_heading'] = NULL;
        if ( preg_match('/styleH="[^"]+";/', $settings, $matches) === 1 ) {
            $quote = strpos ($matches [0], '"');
            $temp = substr ($matches [0], $quote+1);
            $temp = trim ($temp, '";');
            $data ['style_heading'] = $temp.';';
        }

        // Determine text styles per level.
        // Syntax for a style level 1 is "styleL1="color:black;"".
        // The default style is just 'color:black;'.
        for ( $count = 0 ; $count < $data ['maxlevel'] ; $count++ ) {
            $data ['styleL'.($count + 1)] = $this->config->getParam('toc_style');
            if ( preg_match('/styleL'.($count + 1).'="[^"]+";/', $settings, $matches) === 1 ) {
                $quote = strpos ($matches [0], '"');
                $temp = substr ($matches [0], $quote+1);
                $temp = trim ($temp, '";');
                $data ['styleL'.($count + 1)] = $temp.';';
            }
        }
        
        return $data;
    }

    /**
     * Add an item to the TOC
     * (Dummy function required by the Doku_Renderer class)
     *
     * @param string $id       the hash link
     * @param string $text     the text to display
     * @param int    $level    the nesting level
     */
    function toc_additem($id, $text, $level) {}

    /**
     * Return total page width in centimeters
     * (margins are included)
     *
     * @see ODTDocument::getWidth for API wrapper function
     * @see pageFormat::getWidth for more information
     * @author LarsDW223
     */
    function _getPageWidth(){
        return $this->document->getWidth();
    }

    /**
     * Return total page height in centimeters
     * (margins are included)
     *
     * @see ODTDocument::getHeight for API wrapper function
     * @see pageFormat::getHeight for more information
     * @author LarsDW223
     */
    function _getPageHeight(){
        return $this->document->getHeight();
    }

    /**
     * Return left margin in centimeters
     *
     * @see ODTDocument::getMarginLeft for API wrapper function
     * @see pageFormat::getMarginLeft for more information
     * @author LarsDW223
     */
    function _getLeftMargin(){
        return $this->document->getMarginLeft();
    }

    /**
     * Return right margin in centimeters
     *
     * @see ODTDocument::getMarginRight for API wrapper function
     * @see pageFormat::getMarginRight for more information
     * @author LarsDW223
     */
    function _getRightMargin(){
        return $this->document->getMarginRight();
    }

    /**
     * Return top margin in centimeters
     *
     * @see ODTDocument::getMarginTop for API wrapper function
     * @see pageFormat::getMarginTop for more information
     * @author LarsDW223
     */
    function _getTopMargin(){
        return $this->document->getMarginTop();
    }

    /**
     * Return bottom margin in centimeters
     *
     * @see ODTDocument::getMarginBottom for API wrapper function
     * @see pageFormat::getMarginBottom for more information
     * @author LarsDW223
     */
    function _getBottomMargin(){
        return $this->document->getMarginBottom();
    }

    /**
     * Return width percentage value if margins are taken into account.
     * Usually "100%" means 21cm in case of A4 format.
     * But usually you like to take care of margins. This function
     * adjusts the percentage to the value which should be used for margins.
     * So 100% == 21cm e.g. becomes 80.9% == 17cm (assuming a margin of 2 cm on both sides).
     *
     * @param int|string $percentage
     * @return int|string
     * 
     * @see ODTDocument::getRelWidthMindMargins for API wrapper function
     * @see pageFormat::getRelWidthMindMargins for more information
     * @author LarsDW223
     */
    function _getRelWidthMindMargins ($percentage = '100'){
        return $this->document->getRelWidthMindMargins($percentage);
    }

    /**
     * Like _getRelWidthMindMargins but returns the absulute width
     * in centimeters.
     *
     * @param string|int|float $percentage
     * @return float
     * 
     * @see ODTDocument::getAbsWidthMindMargins for API wrapper function
     * @see pageFormat::getAbsWidthMindMargins for more information
     * @author LarsDW223
     */
    function _getAbsWidthMindMargins ($percentage = '100'){
        return $this->document->getAbsWidthMindMargins($percentage);
    }

    /**
     * Return height percentage value if margins are taken into account.
     * Usually "100%" means 29.7cm in case of A4 format.
     * But usually you like to take care of margins. This function
     * adjusts the percentage to the value which should be used for margins.
     * So 100% == 29.7cm e.g. becomes 86.5% == 25.7cm (assuming a margin of 2 cm on top and bottom).
     *
     * @param string|float|int $percentage
     * @return float|string
     * 
     * @see ODTDocument::getRelHeightMindMargins for API wrapper function
     * @see pageFormat::getRelHeightMindMargins for more information
     * @author LarsDW223
     */
    function _getRelHeightMindMargins ($percentage = '100'){
        return $this->document->getRelHeightMindMargins($percentage);
    }

    /**
     * Like _getRelHeightMindMargins but returns the absulute width
     * in centimeters.
     *
     * @param string|int|float $percentage
     * @return float
     * 
     * @see ODTDocument::getAbsHeightMindMargins for API wrapper function
     * @see pageFormat::getAbsHeightMindMargins for more information
     * @author LarsDW223
     */
    function _getAbsHeightMindMargins ($percentage = '100'){
        return $this->document->getAbsHeightMindMargins($percentage);
    }

    /**
     * Render plain text data.
     *
     * @param string $text
     * @see ODTDocument::addPlainText for more information
     */
    function cdata($text) {
        $this->document->addPlainText($text);
    }

    /**
     * Open a paragraph.
     *
     * @param string $style Name of the style to use for the paragraph
     * 
     * @see ODTDocument::paragraphOpen for API wrapper function
     * @see ODTParagraph::paragraphOpen for more information
     */
    function p_open($style=NULL){
        $this->document->paragraphOpen($style);
    }

    /**
     * Close a paragraph.
     *
     * @see ODTDocument::paragraphClose for API wrapper function
     * @see ODTParagraph::paragraphClose for more information
     */
    function p_close(){
        $this->document->paragraphClose();
    }

    /**
     * Set bookmark for the start of the page. This just saves the title temporarily.
     * It is then to be inserted in the first header or paragraph.
     *
     * @param string $id    ID of the bookmark
     */
    function set_page_bookmark($id){
        $this->document->setPageBookmark($id);
    }

    /**
     * Render a heading
     *
     * @param string $text  the text to display
     * @param int    $level header level
     * @param int    $pos   byte position in the original source
     */
    function header($text, $level, $pos){
        $this->document->heading($text, $level);
    }

    function hr() {
        $this->document->horizontalRule();
    }

    function linebreak() {
        $this->document->linebreak();
    }

    function pagebreak() {
        $this->document->pagebreak();
    }

    function strong_open() {
        $this->document->spanOpen($this->document->getStyleName('strong'));
    }

    function strong_close() {
        $this->document->spanClose();
    }

    function emphasis_open() {
        $this->document->spanOpen($this->document->getStyleName('emphasis'));
    }

    function emphasis_close() {
        $this->document->spanClose();
    }

    function underline_open() {
        $this->document->spanOpen($this->document->getStyleName('underline'));
    }

    function underline_close() {
        $this->document->spanClose();
    }

    function monospace_open() {
        $this->document->spanOpen($this->document->getStyleName('monospace'));
    }

    function monospace_close() {
        $this->document->spanClose();
    }

    function subscript_open() {
        $this->document->spanOpen($this->document->getStyleName('sub'));
    }

    function subscript_close() {
        $this->document->spanClose();
    }

    function superscript_open() {
        $this->document->spanOpen($this->document->getStyleName('sup'));
    }

    function superscript_close() {
        $this->document->spanClose();
    }

    function deleted_open() {
        $this->document->spanOpen($this->document->getStyleName('del'));
    }

    function deleted_close() {
        $this->document->spanClose();
    }

    function generateSpansfromHTMLCode($HTMLCode){
        $this->document->generateSpansfromHTMLCode($HTMLCode);
    }

    /*
     * Tables
     */

    /**
     * Start a table
     *
     * @param int $maxcols maximum number of columns
     * @param int $numrows NOT IMPLEMENTED
     */
    function table_open($maxcols = NULL, $numrows = NULL, $pos = NULL){
        $this->document->tableOpen($maxcols, $numrows);
    }

    function table_close($pos = NULL){
        $this->document->tableClose();
    }

    function tablecolumn_add(){
        $this->document->tableAddColumn();
    }

    function tablerow_open(){
        $this->document->tableRowOpen();
    }

    function tablerow_close(){
        $this->document->tableRowClose();
    }

    /**
     * Open a table header cell
     *
     * @param int    $colspan
     * @param string $align left|center|right
     * @param int    $rowspan
     */
    function tableheader_open($colspan = 1, $align = "left", $rowspan = 1){
        $this->document->tableHeaderOpen($colspan, $rowspan, $align);
    }

    function tableheader_close(){
        $this->document->tableHeaderClose();
    }

    /**
     * Open a table cell
     *
     * @param int    $colspan
     * @param string $align left|center|right
     * @param int    $rowspan
     */
    function tablecell_open($colspan = 1, $align = "left", $rowspan = 1){
        $this->document->tableCellOpen($colspan, $rowspan, $align);
    }

    function tablecell_close(){
        $this->document->tableCellClose();
    }

    /**
     * Callback for footnote start syntax.
     *
     * @author Andreas Gohr <andi@splitbrain.org>
     */
    function footnote_open() {
        $this->document->footnoteOpen();
    }

    /**
     * Callback for footnote end syntax.
     *
     * @author Andreas Gohr
     */
    function footnote_close() {
        $this->document->footnoteClose();
    }

    function listu_open($continue=false) {
        $this->document->listOpen($continue, $this->document->getStyleName('list'), 'ul');
    }

    function listu_close() {
        $this->document->listClose();
    }

    function listo_open($continue=false) {
        $this->document->listOpen($continue, $this->document->getStyleName('numbering'), 'ol');
    }

    function listo_close() {
        $this->document->listClose();
    }

    function list_close() {
        $this->document->listClose();
    }

    /**
     * Open a list item
     *
     * @param int $level the nesting level
     */
    function listitem_open($level, $node = false) {
        $this->document->listItemOpen($level);
    }

    function listitem_close() {
        $this->document->listItemClose();
    }

    /**
     * Open a list header
     *
     * @param int $level the nesting level
     */
    function listheader_open($level) {
        $this->document->listHeaderOpen($level);
    }

    function listheader_close() {
        $this->document->listHeaderClose();
    }

    function listcontent_open() {
        $this->document->listContentOpen();
    }

    function listcontent_close() {
        $this->document->listContentClose();
    }

    /**
     * Output unformatted $text
     *
     * @param string $text
     */
    function unformatted($text) {
        $this->document->addPlainText($text);
    }

    /**
     * Format an acronym
     *
     * @param string $acronym
     */
    function acronym($acronym) {
        $this->document->addPlainText($acronym);
    }

    /**
     * @param string $smiley
     */
    function smiley($smiley) {
        if ( array_key_exists($smiley, $this->smileys) ) {
            $src = DOKU_INC."lib/images/smileys/".$this->smileys[$smiley];
            $this->_odtAddImage($src);
        } else {
            $this->document->addPlainText($smiley);
        }
    }

    /**
     * Format an entity
     *
     * @param string $entity
     */
    function entity($entity) {
        // Add plain text will replace entities
        $this->document->addPlainText($entity);
    }

    /**
     * Typographically format a multiply sign
     *
     * Example: ($x=640, $y=480) should result in "640×480"
     *
     * @param string|int $x first value
     * @param string|int $y second value
     */
    function multiplyentity($x, $y) {
        $text .= $x.'×'.$y;
        $this->document->addPlainText($text);
    }

    function singlequoteopening() {
        global $lang;
        $text .= $lang['singlequoteopening'];
        $this->document->addPlainText($text);
    }

    function singlequoteclosing() {
        global $lang;
        $text .= $lang['singlequoteclosing'];
        $this->document->addPlainText($text);
    }

    function apostrophe() {
        global $lang;
        $text .= $lang['apostrophe'];
        $this->document->addPlainText($text);
    }

    function doublequoteopening() {
        global $lang;
        $text .= $lang['doublequoteopening'];
        $this->document->addPlainText($text);
    }

    function doublequoteclosing() {
        global $lang;
        $text .= $lang['doublequoteclosing'];
        $this->document->addPlainText($text);
    }

    /**
     * Output inline PHP code
     *
     * @param string $text The PHP code
     */
    function php($text) {
        $this->monospace_open();
        $this->document->addPlainText($text);
        $this->monospace_close();
    }

    /**
     * Output block level PHP code
     *
     * @param string $text The PHP code
     */
    function phpblock($text) {
        $this->file($text);
    }

    /**
     * Output raw inline HTML
     *
     * @param string $text The HTML
     */
    function html($text) {
        $this->monospace_open();
        $this->document->addPlainText($text);
        $this->monospace_close();
    }

    /**
     * Output raw block-level HTML
     *
     * @param string $text The HTML
     */
    function htmlblock($text) {
        $this->file($text);
    }

    /**
     * Output preformatted text
     *
     * @param string $text
     */
    function preformatted($text) {
        $this->_preformatted($text);
    }

    /**
     * Display text as file content, optionally syntax highlighted
     *
     * @param string $text text to show
     * @param string $language programming language to use for syntax highlighting
     * @param string $filename file path label
     */
    function file($text, $language=null, $filename=null, $options=null) {
        $this->_highlight('file', $text, $language, $options);
    }

    function quote_open() {
        $this->document->quoteOpen();
    }

    function quote_close() {
        $this->document->quoteClose();        
    }

    /**
     * Display text as code content, optionally syntax highlighted
     *
     * @param string $text text to show
     * @param string $language programming language to use for syntax highlighting
     * @param string $filename file path label
     */
    function code($text, $language=null, $filename=null, $options=null) {
        $this->_highlight('code', $text, $language, $options);
    }

    /**
     * @param string $text
     * @param string $style
     * @param bool $notescaped
     */
    function _preformatted($text, $style=null, $notescaped=true) {
        $this->document->addPreformattedText($text, $style, $notescaped);
    }

    /**
     * This function creates the styles required for Geshi-Syntax highlighting.
     * We need two non-standard-ODT-Plugin-styles:
     * Paragraph-Style: We need a paragraph style without top and bottom margin.
     *                  Otherwise there would be a sapce betwwen the source code lines.
     * List-Style: Usually lists are indented. Our list shall not be indented.
     *             (A list will only be created by Geshi if line numbering is enabled)
     */
    protected function createGeshiListStyle () {
        $style_name = 'highlight_list_paragraph_style';
        if (!$this->document->styleExists($style_name)) {
            // For the list paragrpah copy body style and remove margins
            $body = $this->document->getStyleName('body');
            $style = clone($this->document->getStyle($body));
            if ($style != NULL) {
                $style->setProperty('style-name', $style_name);
                $style->setProperty('margin-top', NULL);
                $style->setProperty('margin-bottom', NULL);
                $this->document->addAutomaticStyle($style);
            }
        }
        $style_name = 'highlight_list_numbers_text_style';
        if (!$this->document->styleExists($style_name)) {
            $source_code_style = $this->document->getStyleByAlias('source code');
            $properties = array();
            $properties ['style-name'] = $style_name;
            $properties ['style-display-name'] = 'Source Code Numbers style';
            $properties ['color'] = $source_code_style->getProperty('color');
            if (empty($properties ['color'])) {
                $properties ['color'] = '#000000';
            }
            $this->document->createTextStyle ($properties, true);
        }
        $style_name = 'highlight_list_ol_style';
        if (!$this->document->styleExists($style_name)) {
            // For the list style copy numbering list style and
            // set indentation for level 1 to '0cm'
            $ol = $this->document->getStyleName('numbering');
            $style = clone($this->document->getStyle($ol));
            if ($style != NULL) {
                $style->setProperty('style-name', $style_name);
                $style->setPropertyForLevel(1, 'text-style-name', 'highlight_list_numbers_text_style');
                $style->setPropertyForLevel(1, 'text-align', 'left');
                $style->setPropertyForLevel(1, 'list-level-position-and-space-mode', 'label-alignment');
                $style->setPropertyForLevel(1, 'label-followed-by', 'listtab');
                $style->setPropertyForLevel(1, 'list-tab-stop-position', '1cm');
                $style->setPropertyForLevel(1, 'text-indent', '-1cm');
                $style->setPropertyForLevel(1, 'margin-left', '1cm');
                $this->document->addAutomaticStyle($style);
            }
        }
    }

    /**
     * @param string $type
     * @param string $text
     * @param string $language
     */
    function _highlight($type, $text, $language=null, $options = null) {

        if (is_null($language)) {
            $this->_preformatted($text, $style_name);
            return;
        }

        // Use cached geshi
        $highlighted_code = p_xhtml_cached_geshi($text, $language, '', $options);

        // Create Geshi styles required for ODT and get ODT sourcecode style
        $this->createGeshiListStyle ();
        $source_code_style = $this->document->getStyleByAlias('source code');

        $options = array();
        $options ['escape_content'] = 'false';
        $options ['space'] = 'preserve';
        $options ['media_selector'] = 'screen';
        $options ['element'] = 'pre';
        $options ['style_names'] = 'prefix_and_class';
        $options ['style_names_prefix'] = 'highlight_';
        if (empty($language)) {
            $options ['attributes'] = 'class="code"';
        } else {
            $options ['attributes'] = 'class="code '.$language.'"';
        }
        $options ['list_ol_style'] = 'highlight_list_ol_style';
        $options ['list_p_style'] = 'highlight_list_paragraph_style';
        $options ['p_style'] = $this->document->getStyleName('preformatted');

        // Open table with just one cell
        $this->document->tableOpen();
        $this->document->tableRowOpen();
        $properties = array();
        $properties ['border']           = $source_code_style->getProperty('border');
        $properties ['border-top']       = $source_code_style->getProperty('border-top');
        $properties ['border-right']     = $source_code_style->getProperty('border-right');
        $properties ['border-bottom']    = $source_code_style->getProperty('border-bottom');
        $properties ['border-left']      = $source_code_style->getProperty('border-left');
        $properties ['padding']          = $source_code_style->getProperty('padding');
        $properties ['padding-top']      = $source_code_style->getProperty('padding-top');
        $properties ['padding-right']    = $source_code_style->getProperty('padding-right');
        $properties ['padding-bottom']   = $source_code_style->getProperty('padding-bottom');
        $properties ['padding-left']     = $source_code_style->getProperty('padding-left');
        $properties ['background-color'] = $source_code_style->getProperty('background-color');

        $this->document->tableCellOpenUseProperties($properties);

        // Generate ODT content from Geshi's HTML code
        $this->document->generateODTfromHTMLCode($highlighted_code, $options);

        // Close table
        $this->document->tableCellClose();
        $this->document->tableRowClose();
        $this->document->tableClose();
    }

    /**
     * Render an internal media file
     *
     * @param string $src       media ID
     * @param string $title     descriptive text
     * @param string $align     left|center|right
     * @param int    $width     width of media in pixel
     * @param int    $height    height of media in pixel
     * @param string $cache     cache|recache|nocache
     * @param string $linking   linkonly|detail|nolink
     * @param bool   $returnonly whether to return odt or write to doc attribute
     */
    function internalmedia ($src, $title=NULL, $align=NULL, $width=NULL,
                            $height=NULL, $cache=NULL, $linking=NULL, $returnonly = false) {
        global $ID;
        resolve_mediaid(getNS($ID),$src, $exists);
        list(/* $ext */,$mime) = mimetype($src);

        if ($linking == 'linkonly') {
            $url = str_replace('doku.php?id=','lib/exe/fetch.php?media=',wl($src,'',true));
            if (empty($title)) {
                $title = $src;
            }
            if ($returnonly) {
                return $this->externallink($url, $title, true);
            } else {
                $this->externallink($url, $title);
            }
            return;
        }

        if(substr($mime,0,5) == 'image'){
            $file = mediaFN($src);
            if($returnonly) {
              return $this->_odtAddImage($file, $width, $height, $align, $title, NULL, true);
            } else {
              $this->_odtAddImage($file, $width, $height, $align, $title);
            }
        }else{
/*
            // FIXME build absolute medialink and call externallink()
            $this->code('FIXME internalmedia: '.$src);
*/
            //FIX by EPO/Intersel - create a link to the dokuwiki internal resource
            if (empty($title)) {$title=explode(':',$src); $title=end($title);}
            if($returnonly) {
              return $this->externalmedia(str_replace('doku.php?id=','lib/exe/fetch.php?media=',wl($src,'',true)),$title,
                                        null, null, null, null, null, true);
            } else {
              $this->externalmedia(str_replace('doku.php?id=','lib/exe/fetch.php?media=',wl($src,'',true)),$title,
                                        null, null, null, null, null);
            }
            //End of FIX
        }
    }

    /**
     * Render an external media file
     *
     * @param string $src        full media URL
     * @param string $title      descriptive text
     * @param string $align      left|center|right
     * @param int    $width      width of media in pixel
     * @param int    $height     height of media in pixel
     * @param string $cache      cache|recache|nocache
     * @param string $linking    linkonly|detail|nolink
     * @param bool   $returnonly whether to return odt or write to doc attribute
     */
    function externalmedia ($src, $title=NULL, $align=NULL, $width=NULL,
                            $height=NULL, $cache=NULL, $linking=NULL, $returnonly = false) {
        list($ext,$mime) = mimetype($src);

        if ($linking == 'linkonly') {
            $url = $src;
            if (empty($title)) {
                $title = $src;
            }
            if ($returnonly) {
                return $this->externallink($url, $title, true);
            } else {
                $this->externallink($url, $title);
            }
            return;
        }

        if(substr($mime,0,5) == 'image'){
            $tmp_dir = $this->config->getParam ('tmpdir')."/odt";
            $tmp_name = $tmp_dir."/".md5($src).'.'.$ext;
            $client = new DokuHTTPClient;
            $img = $client->get($src);
            if ($img === FALSE) {
                $tmp_name = $src; // fallback to a simple link
            } else {
                if (!is_dir($tmp_dir)) io_mkdir_p($tmp_dir);
                $tmp_img = fopen($tmp_name, "w") or die("Can't create temp file $tmp_img");
                fwrite($tmp_img, $img);
                fclose($tmp_img);
            }

            $doc = '';
            if ($linking != 'nolink') {
                $doc .= $this->document->openImageLink ($src, $returnonly);
            }
            $doc .= $this->_odtAddImage($tmp_name, $width, $height, $align, $title, $returnonly);
            if ($linking != 'nolink') {
                $doc .= $this->document->closeImageLink ($returnonly);
            }
            if (file_exists($tmp_name)) unlink($tmp_name);

            return $doc;
        }else{
            if($returnonly) {
              return $this->externallink($src,$title,true);
            } else {
              $this->externallink($src,$title);
            }
        }
    }

    /**
     * Render a CamelCase link
     *
     * @param string $link       The link name
     * @param bool   $returnonly whether to return odt or write to doc attribute
     * @see http://en.wikipedia.org/wiki/CamelCase
     */
    function camelcaselink($link, $returnonly = false) {
        if($returnonly) {
          return $this->internallink($link,$link, null, true);
        } else {
          $this->internallink($link, $link);
        }
    }

    /**
     * This function is only used for the DokuWiki specific
     * 'returnonly' behaviour.
     * 
     * @param string $id
     * @param string $name
     */
    function reference($id, $name = NULL) {
        $ret = '<text:a xlink:type="simple" xlink:href="#'.$id.'"';
        if ($name) {
            $ret .= '>'.$this->_xmlEntities($name).'</text:a>';
        } else {
            $ret .= '/>';
        }
        return $ret;
    }

    /**
     * Render a wiki internal link
     *
     * @param string       $id         page ID to link to. eg. 'wiki:syntax'
     * @param string|array $name       name for the link, array for media file
     * @param bool         $returnonly whether to return odt or write to doc attribute
     *
     * @author Andreas Gohr <andi@splitbrain.org>
     */
    function internallink($id, $name = NULL, $returnonly = false) {
        global $ID;
        // default name is based on $id as given
        $default = $this->_simpleTitle($id);
        // now first resolve and clean up the $id
        resolve_pageid(getNS($ID),$id,$exists);
        $name = $this->_getLinkTitle($name, $default, $isImage, $id);

        // build the absolute URL (keeping a hash if any)
        list($id,$hash) = explode('#',$id,2);
        $url = wl($id,'',true);
        if($hash) $url .='#'.$hash;

        if ($ID == $id) {
            if ($hash) {
                $id = $hash;
            }
            if($returnonly) {
                return $this->locallink_with_text($hash, $id, $name, $returnonly);
            } else {
                $this->locallink_with_text($hash, $id, $name, $returnonly);
            }
        } else {
            if($returnonly) {
                return $this->_doLink($url, $name, $returnonly);
            } else {
                $this->_doLink($url, $name, $returnonly);
            }
        }
    }

    /**
     * Add external link
     *
     * @param string       $url        full URL with scheme
     * @param string|array $name       name for the link, array for media file
     * @param bool         $returnonly whether to return odt or write to doc attribute
     */
    function externallink($url, $name = NULL, $returnonly = false) {
        $name = $this->_getLinkTitle($name, $url, $isImage);

        if($returnonly) {
            return $this->_doLink($url, $name, $returnonly);
        } else {
            $this->_doLink($url, $name, $returnonly);
        }
    }

    /**
     * Inserts a local link with text.
     *
     * @fixme add image handling
     *
     * @param string $hash hash link identifier
     * @param string $id   name for the link (the reference)
     * @param string $text text for the link (text inserted instead of reference)
     */
    function locallink_with_text($hash, $id = NULL, $text = NULL, $returnonly = false){
        if (!$returnonly) {
            $id  = $this->_getLinkTitle($id, $hash, $isImage);
            $this->document->insertCrossReference($id, $text);
        } else {
            return reference($hash, $name);
        }
    }

    /**
     * Inserts a local link.
     *
     * @fixme add image handling
     *
     * @param string $hash hash link identifier
     * @param string $name name for the link
     */
    function locallink($hash, $name = NULL){
        $name  = $this->_getLinkTitle($name, $hash, $isImage);
        $this->document->insertCrossReference($hash, $name);
    }

    /**
     * Render an interwiki link
     *
     * You may want to use $this->_resolveInterWiki() here
     *
     * @param string       $match      original link - probably not much use
     * @param string|array $name       name for the link, array for media file
     * @param string       $wikiName   indentifier (shortcut) for the remote wiki
     * @param string       $wikiUri    the fragment parsed from the original link
     * @param bool         $returnonly whether to return odt or write to doc attribute
     */
    function interwikilink($match, $name = NULL, $wikiName, $wikiUri, $returnonly = false) {
        $name  = $this->_getLinkTitle($name, $wikiUri, $isImage);
        $url = $this-> _resolveInterWiki($wikiName,$wikiUri);
        if($returnonly) {
            return $this->_doLink($url, $name, $returnonly);
        } else {
            $this->_doLink($url, $name, $returnonly);
        }
    }

    /**
     * Just print WindowsShare links
     *
     * @fixme add image handling
     *
     * @param string       $url        the link
     * @param string|array $name       name for the link, array for media file
     * @param bool         $returnonly whether to return odt or write to doc attribute
     */
    function windowssharelink($url, $name = NULL, $returnonly = false) {
        $name  = $this->_getLinkTitle($name, $url, $isImage);
        if($returnonly) {
            return $name;
        } else {
            $this->document->addPlainText($name);
        }
    }

    /**
     * Just print email links
     *
     * @fixme add image handling
     *
     * @param string       $address    Email-Address
     * @param string|array $name       name for the link, array for media file
     * @param bool         $returnonly whether to return odt or write to doc attribute
     */
    function emaillink($address, $name = NULL, $returnonly = false) {
        $name  = $this->_getLinkTitle($name, $address, $isImage);
        if($returnonly) {
            return $this->_doLink("mailto:".$address, $name, $returnonly);
        } else {
            $this->_doLink("mailto:".$address, $name, $returnonly);
        }
    }

    /**
     * Add a hyperlink, handling Images correctly
     *
     * @author Andreas Gohr <andi@splitbrain.org>
     *
     * @param string $url
     * @param string|array $name
     */
    function _doLink($url,$name, $returnonly = false){
        $url = $this->_xmlEntities($url);
        $doc = '';
        if(is_array($name)){
            // Images
            $doc .= $this->document->openImageLink ($url, $returnonly);

            if($name['type'] == 'internalmedia'){
                $doc .= $this->internalmedia($name['src'],
                                     $name['title'],
                                     $name['align'],
                                     $name['width'],
                                     $name['height'],
                                     $name['cache'],
                                     $name['linking'],
                                     $returnonly);
            }

            $doc .= $this->document->closeImageLink ($returnonly);
        }else{
            // Text
            $doc .= $this->document->insertHyperlink ($url, $name, NULL, NULL, $returnonly);
        }
        return $doc;
    }

    /**
     * Construct a title and handle images in titles
     *
     * @author Harry Fuecks <hfuecks@gmail.com>
     *
     * @param string|array|null $title
     * @param string $default
     * @param bool|null $isImage
     * @param string $id
     * @return mixed
     */
    function _getLinkTitle($title, $default, & $isImage, $id=null) {
        $isImage = false;
        if ( is_array($title) ) {
            $isImage = true;
            return $title;
        } elseif (is_null($title) || trim($title) == '') {
            if ($this->config->getParam ('useheading') && $id) {
                $heading = p_get_first_heading($id);
                if ($heading) {
                    return $this->_xmlEntities($heading);
                }
            }
            return $this->_xmlEntities($default);
        } else {
            return $this->_xmlEntities($title);
        }
    }

    /**
     * @param string $value
     * @return string
     */
    function _xmlEntities($value) {
        return str_replace( array('&','"',"'",'<','>'), array('&#38;','&#34;','&#39;','&#60;','&#62;'), $value);
    }

    /**
     * Render the output of an RSS feed
     *
     * @param string $url    URL of the feed
     * @param array  $params Finetuning of the output
     */
    function rss ($url,$params){
        global $lang;

        require_once(DOKU_INC . 'inc/FeedParser.php');
        $feed = new FeedParser();
        $feed->feed_url($url);

        //disable warning while fetching
        $elvl = null;
        if (!defined('DOKU_E_LEVEL')) { $elvl = error_reporting(E_ERROR); }
        $rc = $feed->init();
        if (!defined('DOKU_E_LEVEL')) { error_reporting($elvl); }

        //decide on start and end
        if($params['reverse']){
            $mod = -1;
            $start = $feed->get_item_quantity()-1;
            $end   = $start - ($params['max']);
            $end   = ($end < -1) ? -1 : $end;
        }else{
            $mod   = 1;
            $start = 0;
            $end   = $feed->get_item_quantity();
            $end   = ($end > $params['max']) ? $params['max'] : $end;;
        }

        $this->listu_open();
        if($rc){
            for ($x = $start; $x != $end; $x += $mod) {
                $item = $feed->get_item($x);
                $this->document->listItemOpen(0);
                $this->document->listContentOpen();

                $this->externallink($item->get_permalink(),
                                    $item->get_title());
                if($params['author']){
                    $author = $item->get_author(0);
                    if($author){
                        $name = $author->get_name();
                        if(!$name) $name = $author->get_email();
                        if($name) $this->cdata(' '.$lang['by'].' '.$name);
                    }
                }
                if($params['date']){
                    $this->cdata(' ('.$item->get_date($this->config->getParam ('dformat')).')');
                }
                if($params['details']){
                    $this->cdata(strip_tags($item->get_description()));
                }
                $this->document->listContentClose();
                $this->document->listItemClose();
            }
        }else{
            $this->document->listItemOpen(0);
            $this->document->listContentOpen();
            $this->emphasis_open();
            $this->cdata($lang['rssfailed']);
            $this->emphasis_close();
            $this->externallink($url);
            $this->document->listContentClose();
            $this->document->listItemClose();
        }
        $this->listu_close();
    }

    /**
     * Adds the content of $string as a SVG picture to the document.
     * 
     * @see ODTDocument::addStringAsSVGImage for API wrapper function
     * @see ODTImage::addStringAsSVGImage for a detailed description
     */
    function _addStringAsSVGImage($string, $width = NULL, $height = NULL, $align = NULL, $title = NULL, $style = NULL) {
        $this->document->addStringAsSVGImage($string, $width, $height, $align, $title, $style);
    }

    /**
     * The function adds $string as an SVG image file.
     * It does NOT insert the image in the document.
     * 
     * @see ODTDocument::addStringAsSVGImageFile for a detailed description
     * @see ODTImage::addStringAsSVGImageFile for a detailed description
     */
    function _addStringAsSVGImageFile($string) {
        return $this->document->addStringAsSVGImageFile($string);
    }

    /**
     * Adds the image $src as a picture file without adding it to the content
     * of the document. The link name which can be used for the ODT draw:image xlink:href
     * is returned. The caller is responsible for creating the frame and image tag
     * but therefore has full control over it. This means he can also set parameters
     * in the odt frame and image tag which can not be changed using the function _odtAddImage.
     *
     * @author LarsDW223
     *
     * @param string $src
     * @return string
     */
    function _odtAddImageAsFileOnly($src){
        return $this->document->addFileAsPicture($src);
    }

    /**
     * Adds an image $src to the document.
     * 
     * @param string  $src        The path to the image file
     * @param string  $width      Width of the picture (NULL=original size)
     * @param string  $height     Height of the picture (NULL=original size)
     * @param string  $align      Alignment
     * @param string  $title      Title
     * @param string  $style      Optional "draw:style-name"
     * @param boolean $returnonly Only return code
     * 
     * @see ODTDocument::addImage for API wrapper function
     * @see ODTImage::addImage for a detailed description
     */
    function _odtAddImage($src, $width = NULL, $height = NULL, $align = NULL, $title = NULL, $style = NULL, $returnonly = false){
        if ($returnonly) {
            return $this->document->addImage($src, $width, $height, $align, $title, $style, $returnonly);
        } else {
            $this->document->addImage($src, $width, $height, $align, $title, $style, $returnonly);
        }
    }

    /**
     * Adds an image $src to the document using the parameters set in $properties.
     * 
     * @param string  $src        The path to the image file
     * @param array   $properties Properties (width, height... see ODTImage::addImageUseProperties)
     * @param boolean $returnonly Only return code
     * 
     * @see ODTDocument::addImageUseProperties for API wrapper function
     * @see ODTImage::addImageUseProperties for a detailed description
     */
    function _odtAddImageUseProperties($src, array $properties, $returnonly = false){
        if ($returnonly) {
            return $this->document->addImageUseProperties($src, $properties, $returnonly);
        } else {
            $this->document->addImageUseProperties($src, $properties, $returnonly);
        }
    }

    /**
     * The function tries to examine the width and height
     * of the image stored in file $src.
     * 
     * @see ODTDocument::getImageSize for API wrapper function
     * @see ODTUtility::getImageSize for a detailed description
     */
    public function _odtGetImageSize($src, $maxwidth=NULL, $maxheight=NULL){
        return $this->document->getImageSize($src, $maxwidth, $maxheight);
    }

    /**
     * @param string $src
     * @param  $width
     * @param  $height
     * @return array
     */
    function _odtGetImageSizeString($src, $width = NULL, $height = NULL){
        return $this->document->getImageSizeString($src, $width, $height);
    }

    /**
     * Open a span using CSS.
     * 
     * @see ODTDocument::spanOpenUseCSS for API wrapper function
     * @see ODTSpan::spanOpenUseCSS for detailed documentation
     * @author LarsDW223
     */
    function _odtSpanOpenUseCSS($element=NULL, $attributes=NULL, cssimportnew $import=NULL){
        $this->document->spanOpenUseCSS($element, $attributes, $import);
    }

    /**
     * Open a span using properties.
     * 
     * @see ODTDocument::spanOpenUseProperties for API wrapper function
     * @see ODTSpan::spanOpenUseProperties for detailed documentation
     * @author LarsDW223
     */
    function _odtSpanOpenUseProperties($properties){
        $this->document->spanOpenUseProperties($properties);
    }

    function _odtSpanOpen($style_name){
        $this->document->spanOpen($style_name);
    }

    /**
     * This function closes a span (previously opened with _odtSpanOpenUseCSS).
     *
     * @author LarsDW223
     */
    function _odtSpanClose(){
        $this->document->spanClose();
    }

    /**
     * Open a paragraph using CSS.
     * 
     * @see ODTDocument::paragraphOpenUseCSS for API wrapper function
     * @see ODTParagraph::paragraphOpenUseCSS for detailed documentation
     * @author LarsDW223
     */
    function _odtParagraphOpenUseCSS($element=NULL, $attributes=NULL, cssimportnew $import=NULL){
        $this->document->paragraphOpenUseCSS($element, $attributes, $import);
    }

    /**
     * Open a paragraph using properties.
     * 
     * @see ODTDocument::paragraphOpenUseProperties for API wrapper function
     * @see ODTParagraph::paragraphOpenUseProperties for detailed documentation
     * @author LarsDW223
     */
    function _odtParagraphOpenUseProperties($properties){
        $this->document->paragraphOpenUseProperties($properties);
    }

    /**
     * Open a text box using CSS.
     * 
     * @see ODTDocument::openTextBoxUseCSS for API wrapper function
     * @see ODTFrame::openTextBoxUseCSS for detailed documentation
     */
    function _odtOpenTextBoxUseCSS ($element=NULL, $attributes=NULL, cssimportnew $import=NULL) {
        $this->document->openTextBoxUseCSS ($element, $attributes, $import);
    }

    /**
     * This function opens a div. As divs are not supported by ODT, it will be exported as a frame.
     * To be more precise, to frames will be created. One including a picture nad the other including the text.
     * A picture frame will only be created if a 'background-image' is set in the CSS style.
     *
     * The currently supported CSS properties are:
     * background-color, color, padding, margin, display, border-radius, min-height.
     * The background-image is simulated using a picture frame.
     * FIXME: Find a way to successfuly use the background-image in the graphic style (see comments).
     *
     * The div should be closed by calling '_odtDivCloseAsFrame'.
     *
     * @author LarsDW223
     *
     * @param array $properties
     */
    function _odtDivOpenAsFrameUseProperties ($properties) {
        dbg_deprecated('_odtOpenTextBoxUseProperties');
        $this->_odtOpenTextBoxUseProperties ($properties);
    }

    /**
     * This function closes a div/frame (previously opened with _odtDivOpenAsFrameUseCSS).
     *
     * @author LarsDW223
     */
    function _odtDivCloseAsFrame () {
        $this->_odtCloseTextBox();
    }

    /**
     * This function opens a new table using CSS.
     *
     * @author LarsDW223
     * @see ODTDocument::tableOpenUseCSS for API wrapper function
     * @see ODTTable::tableOpenUseCSS for detailed documentation
     */
    function _odtTableOpenUseCSS($maxcols = NULL, $numrows = NULL, $element=NULL, $attributes = NULL, cssimportnew $import = NULL){
        $this->document->tableOpenUseCSS($maxcols, $numrows, $element, $attributes, $import);
    }

    /**
     * This function opens a new table using properties.
     *
     * @author LarsDW223
     * @see ODTDocument::tableOpenUseProperties for API wrapper function
     * @see ODTTable::tableOpenUseProperties for detailed documentation
     */
    function _odtTableOpenUseProperties ($properties, $maxcols = 0, $numrows = 0){
        $this->document->tableOpenUseProperties ($properties, $maxcols, $numrows);
    }

    /**
     * This function closes a table.
     *
     * @author LarsDW223
     * @see ODTDocument::tableClose for API wrapper function
     * @see ODTTable::tableClose for detailed documentation
     */
    function _odtTableClose () {
        $this->document->tableClose();
    }

    /**
     * This function adds a new table column using properties.
     *
     * @author LarsDW223
     * @see ODTDocument::tableAddColumnUseProperties for API wrapper function
     * @see ODTTable::tableAddColumnUseProperties for detailed documentation
     */
    function _odtTableAddColumnUseProperties (array $properties = NULL){
        $this->document->tableAddColumnUseProperties($properties);
    }

    /**
     * This function opens a new table header using CSS.
     * The header should be closed by calling 'tableheader_close()'.
     *
     * @author LarsDW223
     * @see ODTDocument::tableHeaderOpenUseCSS for API wrapper function
     * @see ODTTable::tableHeaderOpenUseCSS for detailed documentation
     */
    function _odtTableHeaderOpenUseCSS($colspan = 1, $rowspan = 1, $element=NULL, $attributes=NULL, cssimportnew $import=NULL){
        $this->document->tableHeaderOpenUseCSS($colspan, $rowspan, $element, $attributes, $import);
    }

    /**
     * This function opens a new table header using properties.
     * The header should be closed by calling 'tableheader_close()'.
     *
     * @author LarsDW223
     * @see ODTDocument::tableHeaderOpenUseProperties for API wrapper function
     * @see ODTTable::tableHeaderOpenUseProperties for detailed documentation
     */
    function _odtTableHeaderOpenUseProperties ($properties = NULL, $colspan = 1, $rowspan = 1){
        $this->document->tableHeaderOpenUseProperties($properties, $colspan = 1, $rowspan = 1);
    }

    /**
     * This function opens a new table row using CSS.
     * The row should be closed by calling 'tablerow_close()'.
     *
     * @author LarsDW223
     * @see ODTDocument::tableRowOpenUseCSS for API wrapper function
     * @see ODTTable::tableRowOpenUseCSS for detailed documentation
     */
    function _odtTableRowOpenUseCSS($element=NULL, $attributes=NULL, cssimportnew $import=NULL){
        $this->document->tableRowOpenUseCSS($element, $attributes, $import);
    }

    /**
     * This function opens a new table row using properties.
     * The row should be closed by calling 'tablerow_close()'.
     *
     * @author LarsDW223
     * @see ODTDocument::tableRowOpenUseProperties for API wrapper function
     * @see ODTTable::tableRowOpenUseProperties for detailed documentation
     */
    function _odtTableRowOpenUseProperties ($properties){
        $this->document->tableRowOpenUseProperties($properties);
    }

    /**
     * This function opens a new table cell using CSS.
     * The cell should be closed by calling 'tablecell_close()'.
     *
     * @author LarsDW223
     * @see ODTDocument::tableCellOpenUseCSS for API wrapper function
     * @see ODTTable::tableCellOpenUseCSS for detailed documentation
     */
    function _odtTableCellOpenUseCSS($colspan = 1, $rowspan = 1, $element=NULL, $attributes=NULL, cssimportnew $import=NULL){
        $this->document->tableCellOpenUseCSS($colspan, $rowspan, $element, $attributes, $import);
    }

    /**
     * This function opens a new table cell using properties.
     * The cell should be closed by calling 'tablecell_close()'.
     *
     * @author LarsDW223
     * @see ODTDocument::tableCellOpenUseProperties for API wrapper function
     * @see ODTTable::tableCellOpenUseProperties for detailed documentation
     */
    function _odtTableCellOpenUseProperties ($properties, $colspan = 1, $rowspan = 1){
        $this->document->tableCellOpenUseProperties($properties, $colspan, $rowspan);
    }

    /**
     * Open a multi column text box in a frame using properties.
     * 
     * @see ODTDocument::openMultiColumnTextBoxUseProperties for API wrapper function
     * @see ODTFrame::openMultiColumnTextBoxUseProperties for detailed documentation
     */
    function _odtOpenMultiColumnFrame ($properties) {
        $this->document->openMultiColumnTextBoxUseProperties($properties);
    }

    /**
     * This function closes a multi column frame (previously opened with _odtOpenMultiColumnFrame).
     *
     * @see ODTDocument::closeTextBox for API wrapper function
     * @see ODTFrame::closeTextBox for detailed documentation
     * @author LarsDW223
     */
    function _odtCloseMultiColumnFrame () {
        $this->document->closeMultiColumnTextBox();
    }

    /**
     * Open a text box in a frame using properties.
     * 
     * @see ODTDocument::openTextBoxUseProperties for API wrapper function
     * @see ODTFrame::openTextBoxUseProperties for detailed documentation
     */
    function _odtOpenTextBoxUseProperties ($properties) {
        $this->document->openTextBoxUseProperties ($properties);
    }

    /**
     * This function closes a textbox.
     *
     * @see ODTDocument::closeTextBox for API wrapper function
     * @see ODTFrame::closeTextBox for detailed documentation
     * @author LarsDW223
     */
    function _odtCloseTextBox () {
        $this->document->closeTextBox();
    }

    /**
     * Open a frame using properties.
     * 
     * @see ODTDocument::openFrameUseProperties for API wrapper function
     * @see ODTFrame::openFrameUseProperties for detailed documentation
     */
    function _odtOpenFrameUseProperties ($properties) {
        $this->document->openFrameUseProperties ($properties);
    }

    /**
     * This function closes a frame.
     *
     * @see ODTDocument::closeFrame for API wrapper function
     * @see ODTFrame::closeFrame for detailed documentation
     * @author LarsDW223
     */
    function _odtCloseFrame () {
        $this->document->closeFrame();
    }

    /**
     * @param array $dest
     * @param $element
     * @param $classString
     * @param $inlineStyle
     */
    public function getODTProperties (&$dest, $element, $classString, $inlineStyle, $media_sel=NULL, $cssId=NULL) {
        if ($media_sel === NULL) {
            $media_sel = $this->config->getParam ('media_sel');
        }
        // Get properties for our class/element from imported CSS
        $this->import->getPropertiesForElement($dest, $element, $classString, $media_sel, $cssId);

        // Interpret and add values from style to our properties
        $this->document->getCSSStylePropertiesForODT($dest, $inlineStyle);

        // Adjust values for ODT
        //foreach ($dest as $property => $value) {
        //    $dest [$property] = $this->adjustValueForODT ($property, $value, 14);
        //}
        $this->document->adjustValuesForODT($dest);
    }

    /**
     * Replace a CSS URL value with the given path.
     * 
     * @param $URL CSS URL e.g. 'url(images/xyz.png);'
     * @param $replacement The local path
     * @return string The resulting complete file path
     */
    public function replaceURLPrefix ($URL, $replacement) {
        return $this->import->replaceURLPrefix ($URL, $replacement);
    }

    /**
     * Convert pixel to points (X axis).
     * 
     * @param $pixel value to convert
     * @return float The converted value in points
     * @see ODTDocument::toPoints for API wrapper function
     * @see ODTUnits::toPoints for detailed documentation
     */
    public function pixelToPointsX ($pixel) {
        return $this->document->toPoints($pixel, 'x');
    }

    /**
     * Convert pixel to points (Y axis).
     * 
     * @param $pixel value to convert
     * @return float The converted value in points
     * @see ODTDocument::toPoints for API wrapper function
     * @see ODTUnits::toPoints for detailed documentation
     */
    public function pixelToPointsY ($pixel) {
        return $this->document->toPoints($pixel, 'y');
    }

    /**
     * Adjust the given property for ODT.
     * 
     * @param $property The property name
     * @param $value The property value
     * @param int $emValue The conversion value for 'em' units
     * @return string The new, adjusted value
     * @see ODTUtility::adjustValueForODT for detailed documentation
     */
    public function adjustValueForODT ($property, $value) {
        return $this->document->adjustValueForODT ($property, $value);
    }

    /**
     * Callback function which adjusts all CSS length values to point.
     * 
     * @param $property The name of the current CSS property, e.g. 'border-left'
     * @param $value The current value from the original CSS code
     * @param $type There are 3 possible values:
     *              - LengthValueXAxis: the property represents a value on the X axis
     *              - LengthValueYAxis: the property represents a value on the Y axis
     *              - CSSValueType::StrokeOrBorderWidth: the property represents a stroke
     *                or border width
     * @return string The new, adjusted value for the property
     */
    public function adjustLengthCallback ($property, $value, $type) {
        // Replace px with pt (px does not seem to be supported by ODT)
        $length = strlen ($value);
        if ( $length > 2 && $value [$length-2] == 'p' && $value [$length-1] == 'x' ) {
            $number = trim($value, 'px');
            switch ($type) {
                case CSSValueType::LengthValueXAxis:
                    $adjusted = $this->pixelToPointsX($number).'pt';
                break;

                case CSSValueType::StrokeOrBorderWidth:
                    switch ($property) {
                        case 'border':
                        case 'border-left':
                        case 'border-right':
                        case 'border-top':
                        case 'border-bottom':
                            // border in ODT spans does not support 'px' units, so we convert it.
                            $adjusted = $this->pixelToPointsY($number).'pt';
                        break;

                        default:
                            $adjusted = $value;
                        break;
                    }
                break;

                case CSSValueType::LengthValueYAxis:
                default:
                    $adjusted = $this->pixelToPointsY($number).'pt';
                break;
            }
            return $adjusted;
        }
        return $value;
    }

    /**
     * This function read the template page and imports all cdata and code content
     * as additional CSS. ATTENTION: this might overwrite already imported styles
     * from an ODT or CSS template file.
     *
     * @param $pagename The name of the template page
     */
    public function read_templatepage ($pagename) {
        $instructions = p_cached_instructions(wikiFN($pagename));
        $text = '';
        foreach($instructions as $instruction) {
            if($instruction[0] == 'code') {
                $text .= $instruction[1][0];
            } elseif ($instruction[0] == 'cdata') {
                $text .= $instruction[1][0];
            }
        }

        $this->document->importCSSFromString
            ($text, $this->config->getParam('media_sel'), array($this, 'replaceURLPrefixesCallback'), true, $this->config->getParam('olist_label_align'));
    }

    /**
     * Get CSS properties for a given element and adjust them for ODT.
     *
     * @see ODTDocument::getODTProperties for more information
     */
    public function getODTPropertiesNew (&$dest, $element, $attributes=NULL, $media_sel=NULL, $inherit=true) {
        if ($media_sel === NULL) {
            $media_sel = $this->config->getParam ('media_sel');
        }
        $this->document->getODTProperties ($dest, $element, $attributes, $media_sel, $inherit);
    }

    public function getODTPropertiesFromElement (&$dest, iElementCSSMatchable $element, $media_sel=NULL, $inherit=true) {
        if ($media_sel === NULL) {
            $media_sel = $this->config->getParam ('media_sel');
        }
        $this->document->getODTPropertiesFromElement ($dest, $element, $media_sel, $inherit);
    }

    /**
     * This function creates a text style.
     * 
     * @see ODTDocument::createTextStyle for detailed desciption.
     */
    public function createTextStyle ($properties, $common=true) {
        $this->document->createTextStyle ($properties, $common);
    }

    /**
     * This function creates a paragraph style.
     * 
     * @see ODTDocument::createParagraphStyle for detailed desciption.
     */
    public function createParagraphStyle ($properties, $common=true) {
        $this->document->createParagraphStyle ($properties, $common);
    }

    /**
     * This function creates a table style.
     * 
     * @see ODTDocument::createTableStyle for detailed desciption.
     */
    public function createTableStyle ($properties, $common=true) {
        $this->document->createTableStyle ($properties, $common);
    }

    /**
     * This function creates a table row style.
     * 
     * @see ODTDocument::createTableRowStyle for detailed desciption.
     */
    public function createTableRowStyle ($properties, $common=true) {
        $this->document->createTableRowStyle ($properties, $common);
    }

    /**
     * This function creates a table cell style.
     * 
     * @see ODTDocument::createTableCellStyle for detailed  desciption.
     */
    public function createTableCellStyle ($properties, $common=true) {
        $this->document->createTableCellStyle ($properties, $common);
    }

    /**
     * This function creates a table column style.
     * 
     * @see ODTDocument::createTableColumnStyle for detailed desciption.
     */
    public function createTableColumnStyle ($properties, $common=true) {
        $this->document->createTableColumnStyle ($properties, $common);
    }

    public function styleExists ($style_name) {
        return $this->document->styleExists($style_name);
    }

    /**
     * Add a user field.
     * (Code has been adopted from the fields plugin)
     *
     * @param string $name The name of the field
     * @param string $value The value of the field
     * @author Aurelien Bompard <aurelien@bompard.org>
     * @see ODTDocument::addUserField for detailed desciption.
     */    
    public function addUserField($name, $value) {
        $this->document->addUserField($name, $value);
    }

    /**
     * Insert a user field reference.
     * (Code has been adopted from the fields plugin)
     *
     * @param string $name The name of the field
     * @author Aurelien Bompard <aurelien@bompard.org>
     * @see ODTDocument::insertUserField for detailed desciption.
     */    
    public function insertUserField($name) {
        $this->document->insertUserField($name);
    }

    protected function buildODTPathes (&$ODTTemplatePath, &$tempDirPath) {
        global $ID;

        // Temp dir
        if (is_dir($this->config->getParam('tmpdir'))) {
            $tempDirPath = $this->config->getParam('tmpdir');
        }
        $tempDirPath = $tempDirPath."/odt/".str_replace(':','-',$ID);

        // Eventually determine ODT template file
        $ODTTemplatePath = NULL;
        $template = $this->config->getParam ('odt_template');
        if (!empty($template)) {
            $ODTTemplatePath = $this->config->getParam('mediadir').'/'.$this->config->getParam ('tpl_dir')."/".$this->config->getParam ('odt_template');
        }
    }    

    public function addToValue ($value, $add) {
        return $this->document->addToValue ($value, $add);
    }

    public function subFromValue ($value, $sub) {
        return $this->document->subFromValue ($value, $sub);
    }

    public function getHTMLStack () {
        return $this->document->getHTMLStack ();
    }

    public function dumpHTMLStack () {
        $this->document->dumpHTMLStack ();
    }

    public function setOrderedListParams ($setLevel, $align, $paddingLeft=0, $marginLeft=1) {
        $this->document->setOrderedListParams($setLevel, $align, $paddingLeft, $marginLeft);
    }

    public function setUnorderedListParams ($setLevel, $align, $paddingLeft=0, $marginLeft=1) {
        $this->document->setUnorderedListParams($setLevel, $align, $paddingLeft, $marginLeft);
    }

    /**
     * Insert a bookmark.
     *
     * @param string $id    ID of the bookmark
     * @param string $now   Insert bookmark immediately?
     * @see ODTDocument::insertBookmark for detailed desciption.
     */
    public function insertBookmark($id, $now=true) {
        $this->document->insertBookmark($id, $now);
    }

    /**
     * Automatically generate ODT elements from HTML code.
     *
     * @param string $html_code The HTML code to convert
     * @param array  $options   Options array (FIXME: documentation needed)
     * @see ODTUtility::generateODTfromHTMLCode for detailed desciption.
     */
    public function generateODTfromHTMLCode($html_code, $options=null) {
        // Generate ODT content from Geshi's HTML code
        $this->document->generateODTfromHTMLCode($html_code, $options);
    }
}

//Setup VIM: ex: et ts=4 enc=utf-8 :
