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