1<?php
2
3// must be run within Dokuwiki
4if(!defined('DOKU_INC')) die();
5
6require_once DOKU_PLUGIN . 'latexport/implementation/decorator.php';
7
8/**
9 * Final tex decorator, takes care of all formatting that does not
10 * require state machines, and stores content to the archive.
11 * Can add more layers of decorators over it, but this decorator has always to
12 * be at the bottom layer.
13 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
14 * @author Jean-Michel Gonet <jmgonet@yahoo.com>
15 */
16class DecoratorPersister extends Decorator {
17
18	/** Where to save images. */
19	const GRAPHICSPATH = 'images/';
20
21	/** Content of the document is saved in the ZIP archive. */
22	private $archive;
23
24	/** Counts the number of matters (frontmatter = 0, mainmatter = 1, etc.) */
25	private $matterNumber;
26
27	private $pageId;
28
29	private $firstHeader;
30
31	/**
32	 * Class constructor.
33	 * @param archive Will receive the content of the document.
34	 */
35	function __construct($archive) {
36		$this->archive = $archive;
37		$this->matterNumber = 0;
38	}
39
40	//////////////////////////////////////////////////////////////////////////////////
41	//////////////////////////////////////////////////////////////////////////////////
42	//                                                                              //
43	//                          Handle latexport syntax.                            //
44	//                                                                              //
45	//////////////////////////////////////////////////////////////////////////////////
46	//////////////////////////////////////////////////////////////////////////////////
47
48	/**
49	 * Receives a local file to include.
50	 * @param $link string Local file to include.
51	 */
52	function input($link) {
53		$this->appendCommand("input", $link);
54	}
55
56	/**
57	 * Adds a latex command to the document.
58	 * @param command  The command
59	 * @param scope    The name of the scope, or the mandatory argument,
60	 *                 to be included inside the curly brackets.
61	 * @param argument If specified, to be included in square brackets. Depending
62	 *                 on the command, square brackets are placed before or after
63	 *                 the curly brackets.
64	 */
65	function appendCommand($command, $scope = null, $argument = '') {
66		$this->appendInlineCommand($command, $scope, $argument);
67		$this->archive->appendContent("\r\n");
68	}
69
70	/**
71	 * Adds a latex command to the document.
72	 * @param command  The command
73	 * @param scope    The name of the scope, or the mandatory argument,
74	 *                 to be included inside the curly brackets.
75	 * @param argument If specified, to be included in square brackets. Depending
76	 *                 on the command, square brackets are placed before or after
77	 *                 the curly brackets.
78	 */
79	function appendInlineCommand($command, $scope = null, $argument = '') {
80		if ($argument) {
81			switch($command) {
82				// Some commands have the optional arguments after the curly brackets:
83				case 'begin':
84				case 'end':
85					switch($scope) {
86						case 'minipage':
87							$text = '\\'.$command.'{'.$scope.'}{'.$argument.'}';
88							break;
89
90						default:
91							$text = '\\'.$command.'{'.$scope.'}['.$argument.']';
92							break;
93					}
94					break;
95
96				// Most commands have the optional arguments before the curly brackets:
97				default:
98					$text = '\\'.$command.'['.$argument.']{'.$scope.'}';
99					break;
100			}
101		}
102		// If there is no argument, then there is only one way to express a command...
103		else {
104			if ($scope) {
105				$text = '\\'.$command.'{'.$scope.'}';
106			}
107			// ... unless there is no scope:
108			else {
109				$text = '\\'.$command;
110			}
111		}
112
113		// Let's render the command:
114		$this->archive->appendContent("$text");
115	}
116
117	/**
118	 * Adds simple content to the document.
119	 * @param c The content.
120	 */
121	function appendContent($c) {
122		$this->archive->appendContent($c);
123	}
124
125	/**
126	 * Renders a label (crossreference) so it can be referenced from elsewhere in the document.
127	 * Places a line break after the label.
128	 * @param link String The label identifier. The method precedes it with the page id. If null,
129	 *                    then the label contains only the page id.
130	 */
131	function appendLabel($link = null) {
132		$this->appendLabelInline($link);
133		$this->appendContent("\r\n");
134	}
135
136	/**
137	 * Renders a label (crossreference) so it can be referenced from elsewhere in the document.
138	 * Does not place a linebreak after the label.
139	 * @param link String The label identifier. The method precedes it with the page id. If null,
140	 *                    then the label contains only the page id.
141	 */
142	function appendLabelInline($link = null) {
143		if ($link) {
144			$label = $this->pageId.':'.$this->texifyReference($link);
145		} else {
146			$label = $this->pageId;
147		}
148		$this->appendInlineCommand('label', $label);
149	}
150
151	//////////////////////////////////////////////////////////////////////////////////
152	//////////////////////////////////////////////////////////////////////////////////
153	//                                                                              //
154	//             Handle plugin syntax like mathjax, anchor...                     //
155	//                                                                              //
156	//////////////////////////////////////////////////////////////////////////////////
157	//////////////////////////////////////////////////////////////////////////////////
158
159	/**
160	 * Receives mathematic formula from Mathjax plugin.
161	 */
162	function mathjax_content($formula) {
163		// The '%' is a comment in latex, you can't use it in Mathjax:
164		$formula = str_replace('%', '\\%', $formula);
165
166   	 	// As Mathjax already uses latex separators, there is no need to reprocess:
167		$this->appendContent("$formula");
168	}
169
170	/**
171	 * Receives the anchors from the 'anchor' plugin.
172	 * @param string $link The anchor name.
173	 * @param string $title The associated text.
174	 */
175	function anchor($link, $title = null) {
176		$this->appendLabelInline($link);
177		$this->appendContent($this->texifyText($title));
178	}
179
180	//////////////////////////////////////////////////////////////////////////////////
181	//////////////////////////////////////////////////////////////////////////////////
182	//                                                                              //
183	//                      Handle standard dokuwiki syntax                         //
184	//                                                                              //
185	//////////////////////////////////////////////////////////////////////////////////
186	//////////////////////////////////////////////////////////////////////////////////
187
188	/**
189	 * Starts rendering a new page.
190	 * @param string $pageId The identifier of the opening page.
191	 * @param int $recursionLevel The level of recursion. When a page includes a page, that's one level of recursion.
192	 */
193	function document_start($pageId = NULL, $recursionLevel = 0) {
194		$this->pageId = $pageId;
195		$this->firstHeader = true;
196	}
197
198	/**
199	 * Ends the document
200	 */
201	function document_end($recursionLevel = 0){
202		if ($recursionLevel == 0) {
203			$this->appendCommand('end', 'document');
204		}
205	}
206
207    /**
208     * Table of content is not rendered in latex.
209     */
210    function render_TOC() {
211		// Do nothing.
212    }
213
214    /**
215     * TOC items are not rendered in latex.
216     */
217    function toc_additem($id, $text, $level) {
218		// Do nothing.
219    }
220
221	/**
222	 * Headers are transformed in part, chapter, section, subsection and subsubsection.
223	 */
224	function header($text, $level, $pos) {
225		switch($level) {
226			case 1:
227				switch($this->matterNumber) {
228					case 0:
229						$this->appendContent("\\mainmatter\r\n");
230						$this->matterNumber = 1;
231						break;
232					case 1:
233						$this->appendContent("\\appendix\r\n");
234						$this->matterNumber = 2;
235						break;
236					default:
237						$this->appendCommand('chapter', $this->texifyText($text));
238						$this->appendLabel($text);
239						break;
240				}
241				break;
242
243			case 2:
244				$this->appendCommand('part', $this->texifyText($text));
245				$this->appendLabel($text);
246				break;
247			case 3:
248				$this->appendCommand('chapter', $this->texifyText($text));
249				$this->appendLabel($text);
250				break;
251			case 4:
252				$this->appendCommand('section', $this->texifyText($text));
253				$this->appendLabel($text);
254				break;
255			default:
256				$this->appendCommand('subsection', $this->texifyText($text));
257				$this->appendLabel($text);
258				break;
259		}
260
261		if ($this->firstHeader) {
262			$this->appendLabel();
263			$this->firstHeader = false;
264		}
265
266	}
267
268    /**
269     * Sections are rendered as title-less headers.
270     * @param int $level section level (as determined by the previous header)
271     */
272    function section_open($level) {
273		// Nothing to do.
274    }
275
276    /**
277     * Close the current section
278     */
279    function section_close() {
280		// Nothing to do.
281    }
282
283	/**
284	 * Renders plain text.
285	 */
286	function cdata($text) {
287		$this->appendContent($this->texifyText($text));
288	}
289
290	/**
291	 * Open a paragraph.
292	 */
293	function p_open() {
294		// Nothing to do.
295	}
296
297	/**
298	 * Close a paragraph.
299	 */
300	function p_close() {
301		$this->appendContent("\r\n\r\n");
302	}
303
304    /**
305     * Create a line break
306     */
307    function linebreak() {
308		$this->appendContent(" \\\\ ");
309    }
310
311    /**
312     * Create a horizontal line
313     */
314    function hr() {
315		$this->appendContent("\r\n\r\n\\noindent\\makebox[\\linewidth]{\\rule{\\paperwidth}{0.4pt}}\r\n");
316    }
317
318	/**
319	 * Start strong (bold) formatting
320	 */
321	function strong_open() {
322		$this->appendContent("\\textbf{");
323	}
324
325	/**
326	 * Stop strong (bold) formatting
327	 */
328	function strong_close() {
329		$this->appendContent("}");
330	}
331
332	/**
333	 * Start emphasis (italics) formatting
334	 */
335	function emphasis_open() {
336		$this->appendContent("\\emph{");
337	}
338
339	/**
340	 * Stop emphasis (italics) formatting
341	 */
342	function emphasis_close() {
343		$this->appendContent("}");
344	}
345
346	/**
347	 * Start underline formatting
348	 */
349	function underline_open() {
350		$this->appendContent("\\underline{");
351	}
352
353	/**
354	 * Stop underline formatting
355	 */
356	function underline_close() {
357		$this->appendContent("}");
358	}
359
360    /**
361     * Start monospace formatting
362     */
363    function monospace_open() {
364		$this->appendContent("\\texttt{");
365    }
366
367    /**
368     * Stop monospace formatting
369     */
370    function monospace_close() {
371		$this->appendContent("}");
372    }
373
374    /**
375     * Start a subscript
376     */
377    function subscript_open() {
378		$this->appendContent("\\textsubscript{");
379    }
380
381    /**
382     * Stop a subscript
383     */
384    function subscript_close() {
385		$this->appendContent("}");
386    }
387
388    /**
389     * Start a superscript
390     */
391    function superscript_open() {
392		$this->appendContent("\\textsuperscript{");
393    }
394
395    /**
396     * Stop a superscript
397     */
398    function superscript_close() {
399		$this->appendContent("}");
400    }
401
402    /**
403     * Start deleted (strike-through) formatting
404     */
405    function deleted_open() {
406		$this->appendContent("\\st{");
407    }
408
409    /**
410     * Stop deleted (strike-through) formatting
411     */
412    function deleted_close() {
413		$this->appendContent("}");
414    }
415
416    /**
417     * Start a footnote
418     */
419    function footnote_open() {
420		$this->appendContent("\\footnote{");
421    }
422
423    /**
424     * Stop a footnote
425     */
426    function footnote_close() {
427		$this->appendContent("}");
428    }
429
430	/**
431	 * Open an unordered list
432	 */
433	function listu_open() {
434		$this->appendCommand('begin', 'itemize');
435	}
436
437	/**
438	 * Close an unordered list
439	 */
440	function listu_close() {
441		$this->appendCommand('end', 'itemize');
442	}
443
444    /**
445     * Open an ordered list
446     */
447    function listo_open() {
448		$this->appendCommand('begin', 'enumerate');
449    }
450
451    /**
452     * Close an ordered list
453     */
454    function listo_close() {
455		$this->appendCommand('end', 'enumerate');
456    }
457
458	/**
459	 * Open a list item
460	 *
461	 * @param int $level the nesting level
462	 * @param bool $node true when a node; false when a leaf
463	 */
464	function listitem_open($level,$node=false) {
465		$this->appendContent(str_repeat('   ', $level).'\\item ');
466	}
467
468	/**
469	 * Start the content of a list item
470	 */
471	function listcontent_open() {
472		// Nothing to do.
473	}
474
475	/**
476	 * Stop the content of a list item
477	 */
478	function listcontent_close() {
479		$this->appendContent("\r\n");
480	}
481
482    /**
483     * Close a list item
484     */
485    function listitem_close() {
486		// Nothing to do.
487    }
488
489    /**
490     * Output unformatted $text
491     *
492     * @param string $text
493     */
494    function unformatted($text) {
495        $this->appendCommand("begin", "verbatim");
496		$this->appendContent($text);
497		$this->appendCommand("end", "verbatim");
498    }
499    /**
500     * Output inline PHP code
501     *
502     * @param string $text The PHP code
503     */
504    function php($text) {
505		$this->monospace_open();
506		$this->cdata($text);
507		$this->monospace_close();
508    }
509
510    /**
511     * Output block level PHP code
512     *
513     * @param string $text The PHP code
514     */
515    function phpblock($text) {
516		$this->appendCommand("begin", "lstlisting", "language=php, style=php-style");
517		$this->appendContent($text);
518		$this->appendCommand("end", "lstlisting");
519    }
520
521    /**
522     * Output raw inline HTML
523     *
524     * If $conf['htmlok'] is true this should add the code as is to $doc
525     *
526     * @param string $text The HTML
527     */
528    function html($text) {
529		$this->monospace_open();
530		$this->cdata($text);
531		$this->monospace_close();
532    }
533
534    /**
535     * Output raw block-level HTML
536     *
537     * If $conf['htmlok'] is true this should add the code as is to $doc
538     *
539     * @param string $text The HTML
540     */
541    function htmlblock($text) {
542		$this->appendCommand("begin", "lstlisting", "language=html, style=html-style");
543		$this->appendContent($text);
544		$this->appendCommand("end", "lstlisting");
545    }
546
547    /**
548     * Output preformatted text
549     *
550     * @param string $text
551     */
552    function preformatted($text) {
553		$this->unformatted($text);
554    }
555
556    /**
557     * Start a block quote
558     */
559    function quote_open() {
560		$this->appendCommand("begin", "displayquote");
561    }
562
563    /**
564     * Stop a block quote
565     */
566    function quote_close() {
567		$this->appendCommand("end", "displayquote");
568    }
569
570    /**
571     * Display text as file content, optionally syntax highlighted
572     *
573     * @param string $text text to show
574     * @param string $lang programming language to use for syntax highlighting
575     * @param string $file file path label
576     */
577    function file($text, $lang = null, $file = null) {
578		if ($file) {
579			$this->unformatted("--> $file");
580		}
581		if ($lang) {
582			$this->appendCommand("begin", "lstlisting", "language=$lang, style=$lang-style");
583		} else {
584			$this->appendCommand("begin", "lstlisting");
585		}
586		$this->appendContent($text);
587		$this->appendCommand("end", "lstlisting");
588    }
589
590    /**
591     * Display text as code content, optionally syntax highlighted
592     *
593     * @param string $text text to show
594     * @param string $lang programming language to use for syntax highlighting
595     * @param string $file file path label
596     */
597    function code($text, $lang = null, $file = null) {
598		$this->file($text, $lang, $file);
599    }
600
601    /**
602     * Format an acronym
603     * Uses $this->acronyms
604     * @param string $acronym
605     */
606    function acronym($acronym) {
607		$this->cdata($acronym);
608    }
609
610    /**
611     * Format a smiley
612     * Uses $this->smiley
613     * @param string $smiley
614     */
615    function smiley($smiley) {
616		$this->cdata($smiley);
617    }
618
619    /**
620     * Format an entity
621     * Entities are basically small text replacements
622     * @param string $entity
623     */
624    function entity($entity) {
625		$this->cdata($entity);
626    }
627
628    /**
629     * Typographically format a multiply sign
630     *
631     * Example: ($x=640, $y=480) should result in "640×480"
632     *
633     * @param string|int $x first value
634     * @param string|int $y second value
635     */
636    function multiplyentity($x, $y) {
637		$this->mathjax_content("\\( $x \\times $y \\)");
638    }
639
640    /**
641     * Render an opening single quote char (language specific)
642     */
643    function singlequoteopening() {
644		$this->cdata("`");
645    }
646
647    /**
648     * Render a closing single quote char (language specific)
649     */
650    function singlequoteclosing() {
651		$this->cdata("´");
652    }
653
654    /**
655     * Render an apostrophe char (language specific)
656     */
657    function apostrophe() {
658		$this->cdata("’");
659    }
660
661    /**
662     * Render an opening double quote char (language specific)
663     */
664    function doublequoteopening() {
665		$this->cdata("“");
666    }
667
668    /**
669     * Render an closinging double quote char (language specific)
670     */
671    function doublequoteclosing() {
672		$this->cdata("”");
673    }
674
675    /**
676     * Render a CamelCase link
677     *
678     * @param string $link The link name
679     * @see http://en.wikipedia.org/wiki/CamelCase
680     */
681    function camelcaselink($link) {
682		$this->externallink($link);
683    }
684
685    /**
686     * Render a page local link
687     *
688     * @param string $hash hash link identifier
689     * @param string $name name for the link
690     */
691    function locallink($hash, $name = null) {
692		$this->internallink($this->pageId.":".$hash, $name);
693    }
694
695	/**
696	 * Render a wiki internal link.
697	 * Internal links at the very beginning of an unordered item include
698	 * the destination page.
699	 * @param string       $link  page ID to link to. eg. 'wiki:syntax'
700	 * @param string|array $title name for the link, array for media file
701	 */
702	function internallink($link, $title = null) {
703		$this->appendContent($this->texifyText($text));
704		$this->appendContent(" ");
705		$this->appendInlineCommand("ref", $this->texifyReference($link));
706	}
707
708    /**
709     * Render an external link
710     *
711     * @param string       $link  full URL with scheme
712     * @param string|array $title name for the link, array for media file
713     */
714    function externallink($link, $title = null) {
715		$this->appendContent($this->texifyText($text).' \\url{'.$link.'}');
716    }
717
718    /**
719     * Render the output of an RSS feed
720     *
721     * @param string $url    URL of the feed
722     * @param array  $params Finetuning of the output
723     */
724    function rss($url, $params) {
725		// Nothing to do.
726    }
727
728    /**
729     * Render an interwiki link
730     *
731     * You may want to use $this->_resolveInterWiki() here
732     *
733     * @param string       $link     original link - probably not much use
734     * @param string|array $title    name for the link, array for media file
735     * @param string       $wikiName indentifier (shortcut) for the remote wiki
736     * @param string       $wikiUri  the fragment parsed from the original link
737     */
738    function interwikilink($link, $title = null, $wikiName, $wikiUri) {
739		$this->externalLink($link, trim($title.' '.$wikiName));
740    }
741
742    /**
743     * Link to file on users OS
744     *
745     * @param string       $link  the link
746     * @param string|array $title name for the link, array for media file
747     */
748    function filelink($link, $title = null) {
749		// Nothing to do.
750    }
751
752    /**
753     * Link to windows share
754     *
755     * @param string       $link  the link
756     * @param string|array $title name for the link, array for media file
757     */
758    function windowssharelink($link, $title = null) {
759		// Nothing to do.
760    }
761
762    /**
763     * Render a linked E-Mail Address
764     *
765     * Should honor $conf['mailguard'] setting
766     *
767     * @param string $address Email-Address
768     * @param string|array $name name for the link, array for media file
769     */
770    function emaillink($address, $name = null) {
771		$this->appendContent("$name \\href{mailto:$address}{$address} ");
772    }
773
774	/**
775	 * Render an internal media file
776	 *
777	 * @param string $src     media ID
778	 * @param string $title   descriptive text
779	 * @param string $align   left|center|right
780	 * @param int    $width   width of media in pixel
781	 * @param int    $height  height of media in pixel
782	 * @param string $cache   cache|recache|nocache
783	 * @param string $linking linkonly|detail|nolink
784	 * @param int    $positionInGroup Position of the media in the group.
785	 * @param int    $totalInGroup Size of the group of media.
786	 */
787	function internalmedia($src, $title = null, $align = null, $width = null,
788	                       $height = null, $cache = null, $linking = null,
789												 $positionInGroup = 0, $totalInGroup = 1) {
790
791		// Find the image and estimate its real size:
792 		$filename = $this->obtainFilename($src);
793 		if (!$this->isPrintable($filename)) {
794 			$this->cdata($title);
795			return;
796		}
797		list($width, $height) = getimagesize($filename);
798
799		// Opens the group of images:
800		if ($positionInGroup == 0) {
801			$this->appendCommand('begin', 'figure', '!htb');
802		}
803
804		// Places the image:
805		$availableSpace = round(1 / $totalInGroup, 1);
806		$sizeInCmAt240ppi = round(2.54 * $width / 240, 1);
807		$angle = 0;
808
809		$this->appendCommand('begin', 'minipage', "$availableSpace\\textwidth");
810		$this->appendCommand('centering');
811
812		$this->appendCommand('includegraphics', $this->insertImage($filename),
813		"width=".$sizeInCmAt240ppi."cm, max width=\\textwidth, angle=$angle");
814
815		if (!empty($title)) {
816			$this->appendCommand('caption', $this->texifyText($title));
817		}
818
819		$this->appendCommand('end', 'minipage');
820
821		// Closes the group of images:
822		if ($positionInGroup == $totalInGroup - 1) {
823			$this->appendCommand('end', 'figure');
824		} else {
825			$this->appendCommand('hfill');
826		}
827	}
828
829	/**
830	 * Returns true if provided filename's extension is of a printable media.
831	 * @param filename String the file name.
832	 * @return boolean true if file is printable.
833	 */
834	private function isPrintable($filename) {
835		$ext = pathinfo($filename, PATHINFO_EXTENSION);
836
837		switch($ext) {
838			case "jpg":
839			case "jpeg":
840			case "gif":
841			case "png":
842				return true;
843
844			default:
845				return false;
846		}
847	}
848
849	/**
850	 * Obtains the filesystem path to the specified resource.
851	 * @param $src String The resource.
852	 * @return String The file name.
853	 */
854	private function obtainFilename($src) {
855		global $ID;
856		list($src, $hash) = explode('#', $src, 2);
857		resolve_mediaid(getNS($ID), $src, $exists, $this->date_at, true);
858		return mediaFN($src);
859	}
860
861	/**
862	 * Inserts the specified file.
863	 * @param The physical path to the file.
864	 * @return The TeX-ified name of the file.
865	 */
866	private function insertImage($filename) {
867		$baseFilename = $this->texifyFilename(basename($filename));
868		$this->archive->insertContent(self::GRAPHICSPATH.$baseFilename, file_get_contents($filename));
869		return $baseFilename;
870	}
871
872    /**
873     * Does not render external media files
874     */
875    function externalmedia($src, $title = null, $align = null, $width = null, $height = null, $cache = null, $linking = null) {
876	   // Nothing to do
877    }
878
879    /**
880     * Render a link to an internal media file.
881     * There is no correct way to render this on a printed media, so we just display the title.
882     */
883    function internalmedialink($src, $title = null, $align = null, $width = null, $height = null, $cache = null) {
884  		$this->cdata($title);
885    }
886
887    /**
888     * Render a link to an external media file as a url.
889     */
890    function externalmedialink($src, $title = null, $align = null, $width = null, $height = null, $cache = null) {
891		$this->externallink($src, $title);
892    }
893
894    /**
895     * Start a table
896     *
897     * @param int $maxcols maximum number of columns
898     * @param int $numrows NOT IMPLEMENTED
899     * @param int $pos     byte position in the original source
900     */
901    function table_open($maxcols = null, $numrows = null, $pos = null) {
902		$this->appendCommand("begin", "table", "h");
903		$this->appendCommand("begin", "center");
904		$this->appendContent("\\begin{tabular}{|".str_repeat("c|", $maxcols)."}\\hline\r\n");
905    }
906
907    /**
908     * Close a table
909     *
910     * @param int $pos byte position in the original source
911     */
912    function table_close($pos = null) {
913		$this->appendCommand("end", "tabular");
914		$this->appendCommand("end", "center");
915		$this->appendCommand("end", "table");
916    }
917
918    /**
919     * Open a table header
920     */
921    function tablethead_open() {
922		// Nothing to do
923    }
924
925    /**
926     * Close a table header
927     */
928    function tablethead_close() {
929		// Nothing to do
930    }
931
932    /**
933     * Open a table body
934     */
935    function tabletbody_open() {
936		// Nothing to do
937    }
938
939    /**
940     * Close a table body
941     */
942    function tabletbody_close() {
943		// Nothing to do
944    }
945
946    /**
947     * Open a table footer
948     */
949    function tabletfoot_open() {
950		// Nothing to do
951    }
952
953    /**
954     * Close a table footer
955     */
956    function tabletfoot_close() {
957		// Nothing to do
958    }
959
960	private $firstCellInRow;
961
962    /**
963     * Open a table row
964     */
965    function tablerow_open() {
966		$this->appendContent("\r\n");
967		$this->firstCellInRow = true;
968    }
969
970    /**
971     * Close a table row
972     */
973    function tablerow_close() {
974		$this->appendContent("\\\\\r\n");
975    }
976
977    /**
978     * Open a table header cell
979     *
980     * @param int    $colspan
981     * @param string $align left|center|right
982     * @param int    $rowspan
983     */
984    function tableheader_open($colspan = 1, $align = null, $rowspan = 1) {
985		if ($this->firstCellInRow) {
986			$this->firstCellInRow = false;
987		} else {
988			$this->appendContent(" &\r\n");
989		}
990		$this->appendContent("    \\multicolumn{".$colspan."}".$this->alignment($align)."{\\multirow{".$rowspan."}{*}{\\thead{");
991    }
992
993    /**
994     * Close a table header cell
995     */
996    function tableheader_close() {
997		$this->appendContent("}}}");
998    }
999
1000    /**
1001     * Open a table cell
1002     *
1003     * @param int    $colspan
1004     * @param string $align left|center|right
1005     * @param int    $rowspan
1006     */
1007    function tablecell_open($colspan = 1, $align = null, $rowspan = 1) {
1008		if ($this->firstCellInRow) {
1009			$this->firstCellInRow = false;
1010		} else {
1011			$this->appendContent(" &\r\n");
1012		}
1013		$this->appendContent("    \\multicolumn{".$colspan."}".$this->alignment($align)."{\\multirow{".$rowspan."}{*}{\\makecell{");
1014    }
1015
1016	function alignment($align) {
1017		switch($align) {
1018			case "left":
1019				return "{|l|}";
1020			case "right":
1021				return "{|r|}";
1022			default:
1023				return "{|c|}";
1024		}
1025	}
1026
1027    /**
1028     * Close a table cell
1029     */
1030    function tablecell_close() {
1031		$this->appendContent("}}}");
1032    }
1033
1034	function table_cline($start, $end) {
1035		$this->appendContent("\\cline{".$start." - ".$end."}");
1036	}
1037
1038}
1039