1 /* This file is part of the KDE project
2  * Copyright (C) 2007, 2009-2010 Thomas Zander <zander@kde.org>
3  * Copyright (C) 2010 Ko Gmbh <cbo@kogmbh.com>
4  * Copyright (C) 2011 Matus Hanzes <matus.hanzes@ixonos.com>
5  * Copyright (C) 2013 C. Boemann <cbo@boemann.dk>
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Library General Public
9  * License as published by the Free Software Foundation; either
10  * version 2 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Library General Public License for more details.
16  *
17  * You should have received a copy of the GNU Library General Public License
18  * along with this library; see the file COPYING.LIB.  If not, write to
19  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
20  * Boston, MA 02110-1301, USA.
21  */
22 
23 #include "KoShapeAnchor.h"
24 #include "KoStyleStack.h"
25 #include "KoOdfLoadingContext.h"
26 
27 #include <KoShapeContainer.h>
28 #include <KoXmlWriter.h>
29 #include <KoXmlReader.h>
30 #include <KoXmlNS.h>
31 #include <KoShapeSavingContext.h>
32 #include <KoShapeLoadingContext.h>
33 
34 #include <QRectF>
35 #include <QTransform>
36 #include <FlakeDebug.h>
37 
38 #include <KoGenChanges.h>
39 
40 class Q_DECL_HIDDEN KoShapeAnchor::Private
41 {
42 public:
Private(KoShape * s)43     Private(KoShape *s)
44             : shape(s)
45             , verticalPos(KoShapeAnchor::VTop)
46             , verticalRel(KoShapeAnchor::VLine)
47             , horizontalPos(KoShapeAnchor::HLeft)
48             , horizontalRel(KoShapeAnchor::HChar)
49             , flowWithText(true)
50             , anchorType(KoShapeAnchor::AnchorToCharacter)
51             , placementStrategy(0)
52             , pageNumber(-1)
53             , textLocation(0)
54     {
55     }
56 
57 
printDebug(QDebug dbg) const58     QDebug printDebug(QDebug dbg) const
59     {
60 #ifndef NDEBUG
61         dbg.space() << "KoShapeAnchor" << this;
62         dbg.space() << "offset:" << offset;
63         dbg.space() << "shape:" << shape->name();
64 #endif
65         return dbg.space();
66     }
67 
68     KoShape * const shape;
69     QPointF offset;
70     KoShapeAnchor::VerticalPos verticalPos;
71     KoShapeAnchor::VerticalRel verticalRel;
72     KoShapeAnchor::HorizontalPos horizontalPos;
73     KoShapeAnchor::HorizontalRel horizontalRel;
74     QString wrapInfluenceOnPosition;
75     bool flowWithText;
76     KoShapeAnchor::AnchorType anchorType;
77     KoShapeAnchor::PlacementStrategy *placementStrategy;
78     int pageNumber;
79     KoShapeAnchor::TextLocation *textLocation;
80 };
81 
KoShapeAnchor(KoShape * shape)82 KoShapeAnchor::KoShapeAnchor(KoShape *shape)
83     : d(new Private(shape))
84 {
85 }
86 
~KoShapeAnchor()87 KoShapeAnchor::~KoShapeAnchor()
88 {
89     if (d->placementStrategy != 0) {
90         delete d->placementStrategy;
91     }
92 }
93 
shape() const94 KoShape *KoShapeAnchor::shape() const
95 {
96     return d->shape;
97 }
98 
anchorType() const99 KoShapeAnchor::AnchorType KoShapeAnchor::anchorType() const
100 {
101     return d->anchorType;
102 }
103 
setHorizontalPos(HorizontalPos hp)104 void KoShapeAnchor::setHorizontalPos(HorizontalPos hp)
105 {
106     d->horizontalPos = hp;
107 }
108 
horizontalPos() const109 KoShapeAnchor::HorizontalPos KoShapeAnchor::horizontalPos() const
110 {
111     return d->horizontalPos;
112 }
113 
setHorizontalRel(HorizontalRel hr)114 void KoShapeAnchor::setHorizontalRel(HorizontalRel hr)
115 {
116     d->horizontalRel = hr;
117 }
118 
horizontalRel() const119 KoShapeAnchor::HorizontalRel KoShapeAnchor::horizontalRel() const
120 {
121     return d->horizontalRel;
122 }
123 
setVerticalPos(VerticalPos vp)124 void KoShapeAnchor::setVerticalPos(VerticalPos vp)
125 {
126     d->verticalPos = vp;
127 }
128 
verticalPos() const129 KoShapeAnchor::VerticalPos KoShapeAnchor::verticalPos() const
130 {
131     return d->verticalPos;
132 }
133 
setVerticalRel(VerticalRel vr)134 void KoShapeAnchor::setVerticalRel(VerticalRel vr)
135 {
136     d->verticalRel = vr;
137 }
138 
verticalRel() const139 KoShapeAnchor::VerticalRel KoShapeAnchor::verticalRel() const
140 {
141     return d->verticalRel;
142 }
143 
wrapInfluenceOnPosition() const144 QString KoShapeAnchor::wrapInfluenceOnPosition() const
145 {
146     return d->wrapInfluenceOnPosition;
147 }
148 
flowWithText() const149 bool KoShapeAnchor::flowWithText() const
150 {
151     return d->flowWithText;
152 }
153 
pageNumber() const154 int KoShapeAnchor::pageNumber() const
155 {
156     return d->pageNumber;
157 }
158 
offset() const159 const QPointF &KoShapeAnchor::offset() const
160 {
161     return d->offset;
162 }
163 
setOffset(const QPointF & offset)164 void KoShapeAnchor::setOffset(const QPointF &offset)
165 {
166     d->offset = offset;
167 }
168 
saveOdf(KoShapeSavingContext & context) const169 void KoShapeAnchor::saveOdf(KoShapeSavingContext &context) const
170 {
171     // anchor-type
172     switch (d->anchorType) {
173     case AnchorToCharacter:
174         shape()->setAdditionalAttribute("text:anchor-type", "char");
175         break;
176     case AnchorAsCharacter:
177         shape()->setAdditionalAttribute("text:anchor-type", "as-char");
178         break;
179     case AnchorParagraph:
180         shape()->setAdditionalAttribute("text:anchor-type", "paragraph");
181         break;
182     case AnchorPage:
183         shape()->setAdditionalAttribute("text:anchor-type", "page");
184         break;
185     default:
186         break;
187     }
188 
189     // vertical-pos
190     switch (d->verticalPos) {
191     case VBelow:
192         shape()->setAdditionalStyleAttribute("style:vertical-pos", "below");
193         break;
194     case VBottom:
195         shape()->setAdditionalStyleAttribute("style:vertical-pos", "bottom");
196         break;
197     case VFromTop:
198         shape()->setAdditionalStyleAttribute("style:vertical-pos", "from-top");
199         break;
200     case VMiddle:
201         shape()->setAdditionalStyleAttribute("style:vertical-pos", "middle");
202         break;
203     case VTop:
204         shape()->setAdditionalStyleAttribute("style:vertical-pos", "top");
205         break;
206     default:
207         break;
208     }
209 
210     // vertical-rel
211     switch (d->verticalRel) {
212     case VBaseline:
213         shape()->setAdditionalStyleAttribute("style:vertical-rel", "baseline");
214         break;
215     case VChar:
216         shape()->setAdditionalStyleAttribute("style:vertical-rel", "char");
217         break;
218     case VFrame:
219         shape()->setAdditionalStyleAttribute("style:vertical-rel", "frame");
220         break;
221     case VFrameContent:
222         shape()->setAdditionalStyleAttribute("style:vertical-rel", "frame-content");
223         break;
224     case VLine:
225         shape()->setAdditionalStyleAttribute("style:vertical-rel", "line");
226         break;
227     case VPage:
228         shape()->setAdditionalStyleAttribute("style:vertical-rel", "page");
229         break;
230     case VPageContent:
231         shape()->setAdditionalStyleAttribute("style:vertical-rel", "page-content");
232         break;
233     case VParagraph:
234         shape()->setAdditionalStyleAttribute("style:vertical-rel", "paragraph");
235         break;
236     case VParagraphContent:
237         shape()->setAdditionalStyleAttribute("style:vertical-rel", "paragraph-content");
238         break;
239     case VText:
240         shape()->setAdditionalStyleAttribute("style:vertical-rel", "text");
241         break;
242     default:
243         break;
244     }
245 
246     // horizontal-pos
247     switch (d->horizontalPos) {
248     case HCenter:
249         shape()->setAdditionalStyleAttribute("style:horizontal-pos", "center");
250         break;
251     case HFromInside:
252         shape()->setAdditionalStyleAttribute("style:horizontal-pos", "from-inside");
253         break;
254     case HFromLeft:
255         shape()->setAdditionalStyleAttribute("style:horizontal-pos", "from-left");
256         break;
257     case HInside:
258         shape()->setAdditionalStyleAttribute("style:horizontal-posl", "inside");
259         break;
260     case HLeft:
261         shape()->setAdditionalStyleAttribute("style:horizontal-pos", "left");
262         break;
263     case HOutside:
264         shape()->setAdditionalStyleAttribute("style:horizontal-pos", "outside");
265         break;
266     case HRight:
267         shape()->setAdditionalStyleAttribute("style:horizontal-pos", "right");
268         break;
269     default:
270         break;
271     }
272 
273     // horizontal-rel
274     switch (d->horizontalRel) {
275     case HChar:
276         shape()->setAdditionalStyleAttribute("style:horizontal-rel", "char");
277         break;
278     case HPage:
279         shape()->setAdditionalStyleAttribute("style:horizontal-rel", "page");
280         break;
281     case HPageContent:
282         shape()->setAdditionalStyleAttribute("style:horizontal-rel", "page-content");
283         break;
284     case HPageStartMargin:
285         shape()->setAdditionalStyleAttribute("style:horizontal-rel", "page-start-margin");
286         break;
287     case HPageEndMargin:
288         shape()->setAdditionalStyleAttribute("style:horizontal-rel", "page-end-margin");
289         break;
290     case HFrame:
291         shape()->setAdditionalStyleAttribute("style:horizontal-rel", "frame");
292         break;
293     case HFrameContent:
294         shape()->setAdditionalStyleAttribute("style:horizontal-rel", "frame-content");
295         break;
296     case HFrameEndMargin:
297         shape()->setAdditionalStyleAttribute("style:horizontal-rel", "frame-end-margin");
298         break;
299     case HFrameStartMargin:
300         shape()->setAdditionalStyleAttribute("style:horizontal-rel", "frame-start-margin");
301         break;
302     case HParagraph:
303         shape()->setAdditionalStyleAttribute("style:horizontal-rel", "paragraph");
304         break;
305     case HParagraphContent:
306         shape()->setAdditionalStyleAttribute("style:horizontal-rel", "paragraph-content");
307         break;
308     case HParagraphEndMargin:
309         shape()->setAdditionalStyleAttribute("style:horizontal-rel", "paragraph-end-margin");
310         break;
311     case HParagraphStartMargin:
312         shape()->setAdditionalStyleAttribute("style:horizontal-rel", "paragraph-start-margin");
313         break;
314     default:
315         break;
316     }
317 
318     if (!d->wrapInfluenceOnPosition.isEmpty()) {
319         shape()->setAdditionalStyleAttribute("draw:wrap-influence-on-position", d->wrapInfluenceOnPosition);
320     }
321 
322     if (d->flowWithText) {
323         shape()->setAdditionalStyleAttribute("style:flow-with-text", "true");
324     } else {
325         shape()->setAdditionalStyleAttribute("style:flow-with-text", "false");
326     }
327 
328     if (shape()->parent()) {// an anchor may not yet have been layout-ed
329         QTransform parentMatrix = shape()->parent()->absoluteTransformation(0).inverted();
330         QTransform shapeMatrix = shape()->absoluteTransformation(0);
331 
332         qreal dx = d->offset.x() - shapeMatrix.dx()*parentMatrix.m11()
333                                    - shapeMatrix.dy()*parentMatrix.m21();
334         qreal dy = d->offset.y() - shapeMatrix.dx()*parentMatrix.m12()
335                                    - shapeMatrix.dy()*parentMatrix.m22();
336         context.addShapeOffset(shape(), QTransform(parentMatrix.m11(),parentMatrix.m12(),
337                                                 parentMatrix.m21(),parentMatrix.m22(),
338                                                 dx,dy));
339     }
340 
341     shape()->saveOdf(context);
342 
343     context.removeShapeOffset(shape());
344 }
345 
loadOdf(const KoXmlElement & element,KoShapeLoadingContext & context)346 bool KoShapeAnchor::loadOdf(const KoXmlElement &element, KoShapeLoadingContext &context)
347 {
348     d->offset = shape()->position();
349 
350     QString anchorType = shape()->additionalAttribute("text:anchor-type");
351 
352     if (anchorType == "char") {
353         d->anchorType = AnchorToCharacter;
354     } else if (anchorType == "as-char") {
355         d->anchorType = AnchorAsCharacter;
356         d->horizontalRel = HChar;
357         d->horizontalPos = HLeft;
358     } else if (anchorType == "paragraph") {
359         d->anchorType = AnchorParagraph;
360     } else {
361         d->anchorType = AnchorPage;
362         // it has different defaults at least LO thinks so - ODF doesn't define defaults for this
363         d->horizontalPos = HFromLeft;
364         d->verticalPos = VFromTop;
365         d->horizontalRel = HPage;
366         d->verticalRel = VPage;
367     }
368 
369     if (anchorType == "page" && shape()->hasAdditionalAttribute("text:anchor-page-number")) {
370         d->pageNumber = shape()->additionalAttribute("text:anchor-page-number").toInt();
371         if (d->pageNumber <= 0) {
372             // invalid if the page-number is invalid (OO.org does the same)
373             // see http://bugs.kde.org/show_bug.cgi?id=281869
374             d->pageNumber = -1;
375         }
376     } else {
377         d->pageNumber = -1;
378     }
379     // always make it invisible or it will create empty rects on the first page
380     // during initial layout. This is because only when we layout it's final page is
381     // the shape moved away from page 1
382     // in KWRootAreaProvider of textlayout it's set back to visible
383     //
384     // FIXME: asfaics svg shapes can be invisible if display=none
385     // see: https://www.w3.org/TR/SVG11/painting.html#VisibilityControl
386     shape()->setVisible(false);
387 
388     // load settings from graphic style
389     KoStyleStack &styleStack = context.odfLoadingContext().styleStack();
390     styleStack.save();
391     if (element.hasAttributeNS(KoXmlNS::draw, "style-name")) {
392         context.odfLoadingContext().fillStyleStack(element, KoXmlNS::draw, "style-name", "graphic");
393         styleStack.setTypeProperties("graphic");
394     }
395     QString verticalPos = styleStack.property(KoXmlNS::style, "vertical-pos");
396     QString verticalRel = styleStack.property(KoXmlNS::style, "vertical-rel");
397     QString horizontalPos = styleStack.property(KoXmlNS::style, "horizontal-pos");
398     QString horizontalRel = styleStack.property(KoXmlNS::style, "horizontal-rel");
399     d->wrapInfluenceOnPosition = styleStack.property(KoXmlNS::draw, "wrap-influence-on-position");
400     QString flowWithText = styleStack.property(KoXmlNS::style, "flow-with-text");
401     d->flowWithText = flowWithText.isEmpty() ? false : flowWithText == "true";
402     styleStack.restore();
403 
404     // vertical-pos
405     if (verticalPos == "below") {//svg:y attribute is ignored
406         d->verticalPos = VBelow;
407         d->offset.setY(0);
408     } else if (verticalPos == "bottom") {//svg:y attribute is ignored
409         d->verticalPos = VBottom;
410         d->offset.setY(-shape()->size().height());
411     } else if (verticalPos == "from-top") {
412         d->verticalPos = VFromTop;
413     } else if (verticalPos == "middle") {//svg:y attribute is ignored
414         d->verticalPos = VMiddle;
415         d->offset.setY(-(shape()->size().height()/2));
416     } else if (verticalPos == "top") {//svg:y attribute is ignored
417         d->verticalPos = VTop;
418         d->offset.setY(0);
419     }
420 
421     // vertical-rel
422     if (verticalRel == "baseline")
423         d->verticalRel = VBaseline;
424     else if (verticalRel == "char")
425         d->verticalRel = VChar;
426     else if (verticalRel == "frame")
427         d->verticalRel = VFrame;
428     else if (verticalRel == "frame-content")
429         d->verticalRel = VFrameContent;
430     else if (verticalRel == "line")
431         d->verticalRel = VLine;
432     else if (verticalRel == "page")
433         d->verticalRel = VPage;
434     else if (verticalRel == "page-content")
435         d->verticalRel = VPageContent;
436     else if (verticalRel == "paragraph")
437         d->verticalRel = VParagraph;
438     else if (verticalRel == "paragraph-content")
439         d->verticalRel = VParagraphContent;
440     else if (verticalRel == "text")
441         d->verticalRel = VText;
442 
443     // horizontal-pos
444     if (horizontalPos == "center") {//svg:x attribute is ignored
445         d->horizontalPos = HCenter;
446         d->offset.setX(-(shape()->size().width()/2));
447     } else if (horizontalPos == "from-inside") {
448         d->horizontalPos = HFromInside;
449     } else if (horizontalPos == "from-left") {
450         d->horizontalPos = HFromLeft;
451     } else if (horizontalPos == "inside") {//svg:x attribute is ignored
452         d->horizontalPos = HInside;
453         d->offset.setX(0);
454     } else if (horizontalPos == "left") {//svg:x attribute is ignored
455         d->horizontalPos = HLeft;
456         d->offset.setX(0);
457     }else if (horizontalPos == "outside") {//svg:x attribute is ignored
458         d->horizontalPos = HOutside;
459         d->offset.setX(-shape()->size().width());
460     }else if (horizontalPos == "right") {//svg:x attribute is ignored
461         d->horizontalPos = HRight;
462         d->offset.setX(-shape()->size().width());
463     }
464 
465     // horizontal-rel
466     if (horizontalRel == "char")
467         d->horizontalRel = HChar;
468     else if (horizontalRel == "page")
469         d->horizontalRel = HPage;
470     else if (horizontalRel == "page-content")
471         d->horizontalRel = HPageContent;
472     else if (horizontalRel == "page-start-margin")
473         d->horizontalRel = HPageStartMargin;
474     else if (horizontalRel == "page-end-margin")
475         d->horizontalRel = HPageEndMargin;
476     else if (horizontalRel == "frame")
477         d->horizontalRel = HFrame;
478     else if (horizontalRel == "frame-content")
479         d->horizontalRel = HFrameContent;
480     else if (horizontalRel == "frame-end-margin")
481         d->horizontalRel = HFrameEndMargin;
482     else if (horizontalRel == "frame-start-margin")
483         d->horizontalRel = HFrameStartMargin;
484     else if (horizontalRel == "paragraph")
485         d->horizontalRel = HParagraph;
486     else if (horizontalRel == "paragraph-content")
487         d->horizontalRel = HParagraphContent;
488     else if (horizontalRel == "paragraph-end-margin")
489         d->horizontalRel = HParagraphEndMargin;
490     else if (horizontalRel == "paragraph-start-margin")
491         d->horizontalRel = HParagraphStartMargin;
492 
493     // if svg:x or svg:y should be ignored set new position
494     shape()->setPosition(d->offset);
495 
496     return true;
497 }
498 
setAnchorType(KoShapeAnchor::AnchorType type)499 void KoShapeAnchor::setAnchorType(KoShapeAnchor::AnchorType type)
500 {
501     d->anchorType = type;
502     if (type == AnchorAsCharacter) {
503         d->horizontalRel = HChar;
504         d->horizontalPos = HLeft;
505     }
506 }
507 
textLocation() const508 KoShapeAnchor::TextLocation *KoShapeAnchor::textLocation() const
509 {
510     return d->textLocation;
511 }
512 
setTextLocation(TextLocation * textLocation)513 void KoShapeAnchor::setTextLocation(TextLocation *textLocation)
514 {
515     d->textLocation = textLocation;
516 }
517 
placementStrategy() const518 KoShapeAnchor::PlacementStrategy *KoShapeAnchor::placementStrategy() const
519 {
520     return d->placementStrategy;
521 }
522 
setPlacementStrategy(PlacementStrategy * placementStrategy)523 void KoShapeAnchor::setPlacementStrategy(PlacementStrategy *placementStrategy)
524 {
525     if (placementStrategy != d->placementStrategy) {
526         delete d->placementStrategy;
527 
528         d->placementStrategy = placementStrategy;
529     }
530 }
531