1<?php
2
3require_once DOKU_PLUGIN.'odt/ODT/elements/ODTStateElement.php';
4
5/**
6 * ODTElementFrame:
7 * Class for handling the frame element.
8 *
9 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
10 * @author  LarsDW223
11 */
12class ODTElementFrame extends ODTStateElement implements iContainerAccess
13{
14    protected $container = NULL;
15    protected $containerPos = NULL;
16    protected $attributes = NULL;
17    protected $own_max_width = NULL;
18    protected $nameAttr = NULL;
19    protected $name = NULL;
20    protected $written = false;
21
22    /**
23     * Constructor.
24     */
25    public function __construct($style_name=NULL) {
26        parent::__construct();
27        $this->setClass ('frame');
28        if ($style_name != NULL) {
29            $this->setStyleName ($style_name);
30        }
31        $this->container = new ODTContainerElement($this);
32    }
33
34    /**
35     * Return the elements name.
36     *
37     * @return string The ODT XML element name.
38     */
39    public function getElementName () {
40        return ('draw:frame');
41    }
42
43    /**
44     * Return string with encoded opening tag.
45     *
46     * @return string The ODT XML code to open this element.
47     */
48    public function getOpeningTag (ODTInternalParams $params=NULL) {
49        // Convert width to points
50        $width = $this->getWidth();
51        if ($width !== NULL) {
52            $width = $params->units->toPoints($width);
53            $this->setWidth($width);
54        }
55
56        $encoded =  '<draw:frame draw:style-name="'.$this->getStyleName().'" ';
57        $encoded .= $this->getAttributes().'>';
58
59        $this->written = true;
60
61        return $encoded;
62    }
63
64    /**
65     * Return string with encoded closing tag.
66     *
67     * @return string The ODT XML code to close this element.
68     */
69    public function getClosingTag () {
70        return '</draw:frame>';
71    }
72
73    /**
74     * Are we in a paragraph or not?
75     * As a frame we are not.
76     *
77     * @return boolean
78     */
79    public function getInParagraph() {
80        return false;
81    }
82
83    /**
84     * Determine and set the parent for this element.
85     * As a frame the previous element is our parent.
86     *
87     * @param ODTStateElement $previous
88     */
89    public function determineParent(ODTStateElement $previous) {
90        $this->container->determineParent($previous);
91        if ($this->isNested ()) {
92            $this->containerPos = array();
93            $this->getParent()->determinePositionInContainer($this->containerPos, $previous);
94        }
95
96        //$this->setParent($previous);
97    }
98
99    /**
100     * Set frame attributes
101     *
102     * @param array $value
103     */
104    public function setAttributes($value) {
105        // Delete linebreaks and multiple whitespace
106        $this->attributes = preg_replace( "/\r|\n/", "", $value);
107        $this->attributes = preg_replace( "/\s+/", " ", $this->attributes);
108
109        // Save name for later width rewriting
110        $this->nameAttr = $this->getNameAttribute();
111        $this->name = $this->getName();
112    }
113
114    /**
115     * Get frame attributes
116     *
117     * @return array
118     */
119    public function getAttributes() {
120        return $this->attributes;
121    }
122
123    /**
124     * Is this frame a nested frame (inserted into another table/frame)?
125     *
126     * @return boolean
127     */
128    public function isNested () {
129        return $this->container->isNested();
130    }
131
132    public function addNestedContainer (iContainerAccess $nested) {
133        $this->container->addNestedContainer ($nested);
134    }
135
136    public function getNestedContainers () {
137        return $this->container->getNestedContainers ();
138    }
139
140    public function determinePositionInContainer (array &$data, ODTStateElement $current) {
141        // Position in frame doesn't mater for width calculation
142        // So this is a dummy for now
143        $data ['frame'] = true;
144    }
145
146    public function getMaxWidthOfNestedContainer (ODTInternalParams $params, array $data) {
147        if ($this->own_max_width === NULL) {
148            // We do not know our own width yet. Calculate it first.
149            $this->own_max_width = $this->getMaxWidth($params);
150
151            // Re-Write our width if frame already has been written to the document
152            if ($this->written) {
153                if (preg_match('/<draw:frame[^<]*'.$this->nameAttr.'[^>]*>/', $params->content, $matches) === 1) {
154                    $frameTag = $matches [0];
155                    $frameTag = preg_replace('/svg:width="[^"]*"/', 'svg:width="'.$this->own_max_width.'"', $frameTag);
156
157                    // Replace old frame tag in document in
158                    $params->content = str_replace ($matches [0], $frameTag, $params->content);
159                }
160            }
161        }
162
163        // Convert to points
164        if ($this->own_max_width !== NULL) {
165            $width = $params->units->getDigits ($params->units->toPoints($this->own_max_width));
166        }
167
168        return $width.'pt';
169    }
170
171    public function getMaxWidth (ODTInternalParams $params) {
172        if ($this->own_max_width !== NULL) {
173            return $this->own_max_width;
174        }
175        $frameStyle = $this->getStyle();
176
177        // Get frame left margin
178        $leftMargin = $frameStyle->getProperty('margin-left');
179        if ($leftMargin == NULL) {
180            $leftMarginPt = 0;
181        } else {
182            $leftMarginPt = $params->units->getDigits ($params->units->toPoints($leftMargin));
183        }
184
185        // Get frame right margin
186        $rightMargin = $frameStyle->getProperty('margin-right');
187        if ($rightMargin == NULL) {
188            $rightMarginPt = 0;
189        } else {
190            $rightMarginPt = $params->units->getDigits ($params->units->toPoints($rightMargin));
191        }
192
193        // Get available max width
194        if (!$this->isNested ()) {
195            // Get max page width in points.
196            $maxWidth = $params->document->getAbsWidthMindMargins ();
197            $maxWidthPt = $params->units->getDigits ($params->units->toPoints($maxWidth.'cm'));
198        } else {
199            // If this frame is nested in another container we have to ask it's parent
200            // for the allowed max width
201            $maxWidth = $this->getParent()->getMaxWidthOfNestedContainer($params, $this->containerPos);
202            $maxWidthPt = $params->units->getDigits ($params->units->toPoints($maxWidth));
203        }
204
205        // Get frame width
206        $width = $this->getWidth();
207        if ($width !== NULL) {
208            if ($width [strlen($width)-1] != '%') {
209                $widthPt = $params->units->getDigits ($params->units->toPoints($width));
210            } else {
211                $percentage = trim ($width, '%');
212                $widthPt = ($percentage * $maxWidthPt)/100;
213            }
214        }
215
216        // Calculate final width.
217        // If no frame width is set or the frame width is greater than
218        // the calculated max width then use the max width.
219        $maxWidthPt = $maxWidthPt - $leftMarginPt - $rightMarginPt;
220        if ($width == NULL || $widthPt > $maxWidthPt) {
221            $width = $maxWidthPt - $leftMarginPt - $rightMarginPt;
222        } else {
223            $width = $widthPt;
224        }
225        $width = $width.'pt';
226
227        return $width;
228    }
229
230    public function getWidth() {
231        if ($this->attributes !== NULL) {
232            if ( preg_match('/svg:width="[^"]+"/', $this->attributes, $matches) === 1 ) {
233                $width = substr ($matches [0], 11);
234                $width = trim ($width, '"');
235                return $width;
236            }
237        }
238        return NULL;
239    }
240
241    public function setWidth($width) {
242        if ($this->attributes !== NULL) {
243            if ( preg_match('/svg:width="[^"]+"/', $this->attributes, $matches) === 1 ) {
244                $widthAttr = 'svg:width="'.$width.'"';
245                $this->attributes = str_replace($matches [0], $widthAttr, $this->attributes);
246                return;
247            }
248        }
249        $this->attributes .= ' svg:width="'.$width.'"';
250    }
251
252    public function getNameAttribute() {
253        if ($this->attributes !== NULL) {
254            if ( preg_match('/draw:name="[^"]+"/', $this->attributes, $matches) === 1 ) {
255                return $matches [0];
256            }
257        }
258        return NULL;
259    }
260
261    public function getName() {
262        if ($this->attributes !== NULL) {
263            if ( preg_match('/draw:name="[^"]+"/', $this->attributes, $matches) === 1 ) {
264                $name = substr ($matches [0], 10);
265                $name = trim ($name, '"');
266                return $name;
267            }
268        }
269        return NULL;
270    }
271
272    /**
273     * This function adjust the width of the frame.
274     * There is not much to do except conversion of relative to absolute values
275     * and calling the method for all nested elements.
276     * (table has got more work to do, see ODTElementTable::adjustWidth)
277     *
278     * @param ODTInternalParams $params      Common ODT params
279     * @param boolean           $allowNested Allow to process call if this frame is nested
280     */
281    public function adjustWidth (ODTInternalParams $params, $allowNested=false) {
282        if ($this->isNested () && !$allowNested) {
283            // Do not do anything if this is a nested table.
284            // Only if the function is called for the parent/root table
285            // then the width of the nested tables will be calculated.
286            return;
287        }
288        $matches = array ();
289
290        $max_width = $this->getMaxWidth($params);
291        //FIXME: convert % to points
292
293        // Now adjust all nested containers too
294        $nested = $this->getNestedContainers ();
295        foreach ($nested as $container) {
296            $container->adjustWidth ($params, true);
297        }
298    }
299}
300