1 // --------------------------------------------------------------------
2 // A page of a document.
3 // --------------------------------------------------------------------
4 /*
5 
6     This file is part of the extensible drawing editor Ipe.
7     Copyright (c) 1993-2020 Otfried Cheong
8 
9     Ipe is free software; you can redistribute it and/or modify it
10     under the terms of the GNU General Public License as published by
11     the Free Software Foundation; either version 3 of the License, or
12     (at your option) any later version.
13 
14     As a special exception, you have permission to link Ipe with the
15     CGAL library and distribute executables, as long as you follow the
16     requirements of the Gnu General Public License in regard to all of
17     the software in the executable aside from CGAL.
18 
19     Ipe is distributed in the hope that it will be useful, but WITHOUT
20     ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
21     or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
22     License for more details.
23 
24     You should have received a copy of the GNU General Public License
25     along with Ipe; if not, you can find it at
26     "http://www.gnu.org/copyleft/gpl.html", or write to the Free
27     Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
28 
29 */
30 
31 #include "ipepage.h"
32 #include "ipegroup.h"
33 #include "ipereference.h"
34 #include "ipepainter.h"
35 #include "ipeiml.h"
36 #include "ipeutils.h"
37 
38 using namespace ipe;
39 
40 // --------------------------------------------------------------------
41 
42 /*! \class ipe::Page
43   \ingroup doc
44   \brief An Ipe document page.
45 
46   Its main ingredients are a sequence of Objects (with selection
47   state, layer, and a cached bounding box), a set of Layers, and
48   a sequence of Views.
49 
50   Each object on a Page belongs to one of the layers of the page.
51   Layers are orthogonal to the back-to-front ordering of objects (in
52   particular, they are not "layered" - the word is a misnomer).  The
53   "layer" is really just another attribute of the object.
54 
55   - Layers are either editable or locked.  Objects in a locked layer
56     cannot be selected, and a locked layer cannot be made active in
57     the UI.  This more or less means that the contents of such a layer
58     cannot be modified---but that's a consequence of the UI, Ipelib
59     contains no special handling of locked layers.
60 
61   - A layer may have snapping on or off---objects will behave
62     magnetically only if their layer has snapping on.
63 
64 
65   A Page is presented in a number of \e views.  Each view presents
66   some of the layers of the page.  In addition, each view has an
67   active layer (where objects are added when this view is shown in the
68   UI), and possibly a transition effect (Acrobat Reader eye candy).
69 
70   A Page can be copied and assigned.  The operation takes time linear
71   in the number of top-level object on the page.
72 
73 */
74 
75 //! The default constructor creates a new empty page.
76 /*! This page still needs a layer and a view to be usable! */
Page()77 Page::Page() : iTitle()
78 {
79   iUseTitle[0] = iUseTitle[1] = false;
80   iMarked = true;
81 }
82 
83 //! Create a new empty page with standard settings.
84 /*! This is an empty page with layer 'alpha' and a single view. */
basic()85 Page *Page::basic()
86 {
87   // Create one empty page
88   Page *page = new Page;
89   page->addLayer("alpha");
90   page->insertView(0, "alpha");
91   page->setVisible(0, "alpha", true);
92   return page;
93 }
94 
95 // --------------------------------------------------------------------
96 
97 //! save page in XML format.
saveAsXml(Stream & stream) const98 void Page::saveAsXml(Stream &stream) const
99 {
100   stream << "<page";
101   if (!title().empty()) {
102     stream << " title=\"";
103     stream.putXmlString(title());
104     stream << "\"";
105   }
106   if (iUseTitle[0]) {
107     stream << " section=\"\"";
108   } else if (!iSection[0].empty()) {
109     stream << " section=\"";
110     stream.putXmlString(iSection[0]);
111     stream << "\"";
112   }
113   if (iUseTitle[1]) {
114     stream << " subsection=\"\"";
115   } else if (!iSection[1].empty()) {
116     stream << " subsection=\"";
117     stream.putXmlString(iSection[1]);
118     stream << "\"";
119   }
120   if (!iMarked)
121     stream << " marked=\"no\"";
122 
123   stream << ">\n";
124   if (!iNotes.empty()) {
125     stream << "<notes>";
126     stream.putXmlString(iNotes);
127     stream << "</notes>\n";
128   }
129   for (int i = 0; i < countLayers(); ++i) {
130     stream << "<layer name=\"" << iLayers[i].iName << "\"";
131     if (iLayers[i].locked)
132       stream << " edit=\"no\"";
133     if (iLayers[i].snapMode == SnapMode::Never)
134       stream << " snap=\"never\"";
135     else if (iLayers[i].snapMode == SnapMode::Always)
136       stream << " snap=\"always\"";
137     if (!iLayers[i].iData.empty()) {
138       stream << " data=\"";
139       stream.putXmlString(iLayers[i].iData);
140       stream << "\"";
141     }
142     stream << "/>\n";
143   }
144   for (int i = 0; i < countViews(); ++i) {
145     stream << "<view layers=\"";
146     String sep;
147     for (int l = 0; l < countLayers(); ++l) {
148       if (visible(i, l)) {
149 	stream << sep << layer(l);
150 	sep = " ";
151       }
152     }
153     stream << "\"";
154     if (!active(i).empty())
155       stream << " active=\"" << active(i) << "\"";
156     if (!effect(i).isNormal())
157       stream << " effect=\"" << effect(i).string() << "\"";
158     if (markedView(i))
159       stream << " marked=\"yes\"";
160     if (!viewName(i).empty())
161       stream << " name=\"" << viewName(i) << "\"";
162     const AttributeMap &map = viewMap(i);
163     if (map.count() == 0 && iViews[i].iLayerMatrices.empty()) {
164       stream << "/>\n";
165     } else {
166       stream << ">\n";
167       map.saveAsXml(stream);
168       for (const auto &s : iViews[i].iLayerMatrices) {
169 	stream << "<transform layer=\"" << s.iLayer << "\" matrix=\""
170 	       << s.iMatrix << "\"/>\n";
171       }
172       stream << "</view>\n";
173     }
174   }
175   int currentLayer = -1;
176   for (ObjSeq::const_iterator it = iObjects.begin();
177        it != iObjects.end(); ++it) {
178     String l;
179     if (it->iLayer != currentLayer) {
180       currentLayer = it->iLayer;
181       l = layer(currentLayer);
182     }
183     it->iObject->saveAsXml(stream, l);
184   }
185   stream << "</page>\n";
186 }
187 
188 // --------------------------------------------------------------------
189 
SLayer(String name)190 Page::SLayer::SLayer(String name)
191 {
192   iName = name;
193   locked = false;
194   snapMode = SnapMode::Visible;
195 }
196 
197 //! Set free data field of the layer.
setLayerData(int index,String data)198 void Page::setLayerData(int index, String data)
199 {
200   iLayers[index].iData = data;
201 }
202 
203 //! Set locking of layer \a i.
setLocked(int i,bool flag)204 void Page::setLocked(int i, bool flag)
205 {
206   iLayers[i].locked = flag;
207 }
208 
209 //! Set snapping of layer \a i.
setSnapping(int i,SnapMode mode)210 void Page::setSnapping(int i, SnapMode mode)
211 {
212   iLayers[i].snapMode = mode;
213 }
214 
215 //! Add a new layer.
addLayer(String name)216 void Page::addLayer(String name)
217 {
218   iLayers.push_back(SLayer(name));
219   iLayers.back().iVisible.resize(countViews());
220   for (int i = 0; i < countViews(); ++i)
221     iLayers.back().iVisible[i] = false;
222 }
223 
224 //! Find layer with given name.
225 /*! Returns -1 if not found. */
findLayer(String name) const226 int Page::findLayer(String name) const
227 {
228   for (int i = 0; i < countLayers(); ++i)
229     if (layer(i) == name)
230       return i;
231   return -1;
232 }
233 
234 const char * const layerNames[] = {
235   "alpha", "beta", "gamma", "delta", "epsilon", "zeta", "eta",
236   "theta", "iota", "kappa", "lambda", "mu", "nu", "xi",
237   "omicron", "pi", "rho", "sigma", "tau", "upsilon", "phi",
238   "chi", "psi", "omega" };
239 
240 //! Add a new layer with unique name.
addLayer()241 void Page::addLayer()
242 {
243   for (int i = 0; i < int(sizeof(layerNames)/sizeof(const char *)); ++i) {
244     if (findLayer(layerNames[i]) < 0) {
245       addLayer(layerNames[i]);
246       return;
247     }
248   }
249   char name[20];
250   int i = 1;
251   for (;;) {
252     std::sprintf(name, "alpha%d", i);
253     if (findLayer(name) < 0) {
254       addLayer(name);
255       return;
256     }
257     i++;
258   }
259 }
260 
261 //! Moves the position of a layer in the layer list.
moveLayer(int index,int newIndex)262 void Page::moveLayer(int index, int newIndex)
263 {
264   assert(0 <= index && index < int(iLayers.size())
265 	 && 0 <= newIndex && newIndex < int(iLayers.size()));
266 
267   SLayer layer = iLayers[index];
268   iLayers.erase(iLayers.begin() + index);
269   iLayers.insert(iLayers.begin() + newIndex, layer);
270 
271   // Layer of object needs to be decreased by one if > index
272   // Then increase by one if >= newIndex
273   // If == index, then replace by newIndex
274   for (ObjSeq::iterator it = iObjects.begin(); it != iObjects.end(); ++it) {
275     int k = it->iLayer;
276     if (k == index) {
277       k = newIndex;
278     } else {
279       if (k > index)
280 	k -= 1;
281       if (k >= newIndex)
282 	k += 1;
283     }
284     it->iLayer = k;
285   }
286 }
287 
288 //! Removes an empty layer from the page.
289 /*! All objects are adjusted.  Panics if there are objects in the
290   deleted layer, of if it is the only layer.
291 */
removeLayer(String name)292 void Page::removeLayer(String name)
293 {
294   int index = findLayer(name);
295   assert(iLayers.size() > 1 && index >= 0);
296   for (ObjSeq::iterator it = iObjects.begin(); it != iObjects.end(); ++it) {
297     int k = it->iLayer;
298     assert(k != index);
299     if (k > index)
300       it->iLayer = k-1;
301   }
302   iLayers.erase(iLayers.begin() + index);
303 }
304 
305 //! Return number of objects in each layer
objectsPerLayer(std::vector<int> & objcounts) const306 void Page::objectsPerLayer(std::vector<int> &objcounts) const
307 {
308   objcounts.clear();
309   for (int i = 0; i < countLayers(); ++i)
310     objcounts.push_back(0);
311   for (const auto & obj : iObjects)
312     objcounts[obj.iLayer] += 1;
313 }
314 
315 //! Rename a layer.
renameLayer(String oldName,String newName)316 void Page::renameLayer(String oldName, String newName)
317 {
318   int l = findLayer(oldName);
319   if (l < 0)
320     return;
321   iLayers[l].iName = newName;
322 }
323 
324 //! Returns a precise bounding box for the artwork on the page.
325 /*! This is meant to be used as the bounding box in PDF output.  It is
326   computed by rendering all objects on the page that are visible in at
327   least one view, plus all objects in a layer named "BBOX" (even if
328   that is not visible), using a BBoxPainter. */
pageBBox(const Cascade * sheet) const329 Rect Page::pageBBox(const Cascade *sheet) const
330 {
331   // make table with all layers to be rendered
332   std::vector<bool> layers;
333   for (int l = 0; l < countLayers(); ++l) {
334     bool vis = false;
335     if (layer(l) == "BBOX")
336       vis = true;
337     else
338       for (int vno = 0; !vis && vno < countViews(); ++vno)
339 	vis = visible(vno, l);
340     layers.push_back(vis);
341   }
342 
343   BBoxPainter bboxPainter(sheet);
344   for (int i = 0; i < count(); ++i) {
345     if (layers[layerOf(i)])
346       object(i)->draw(bboxPainter);
347   }
348   return bboxPainter.bbox();
349 }
350 
351 //! Returns a precise bounding box for the artwork in the view.
352 /*! This is meant to be used as the bounding box in PDF and EPS
353   output.  It is computed by rendering all objects in the page using a
354   BBoxPainter. */
viewBBox(const Cascade * sheet,int view) const355 Rect Page::viewBBox(const Cascade *sheet, int view) const
356 {
357   BBoxPainter bboxPainter(sheet);
358   for (int i = 0; i < count(); ++i) {
359     if (objectVisible(view, i))
360       object(i)->draw(bboxPainter);
361   }
362   return bboxPainter.bbox();
363 }
364 
365 //! Snapping occurs if the layer is visible and has snapping enabled.
objSnapsInView(int objNo,int view) const366 bool Page::objSnapsInView(int objNo, int view) const noexcept
367 {
368   int layer = layerOf(objNo);
369   switch (snapping(layer)) {
370   case SnapMode::Visible:
371     return visible(view, layer);
372   case SnapMode::Always:
373     return true;
374   case SnapMode::Never:
375   default:
376     return false;
377   }
378 }
379 
380 // --------------------------------------------------------------------
381 
382 //! Set effect of view.
383 /*! Panics if \a sym is not symbolic. */
setEffect(int index,Attribute sym)384 void Page::setEffect(int index, Attribute sym)
385 {
386   assert(sym.isSymbolic());
387   iViews[index].iEffect = sym;
388 }
389 
390 //! Set active layer of view.
setActive(int index,String layer)391 void Page::setActive(int index, String layer)
392 {
393   assert(findLayer(layer) >= 0);
394   iViews[index].iActive = layer;
395 }
396 
397 //! Set visibility of layer \a layer in view \a view.
setVisible(int view,String layer,bool vis)398 void Page::setVisible(int view, String layer, bool vis)
399 {
400   int index = findLayer(layer);
401   assert(index >= 0);
402   iLayers[index].iVisible[view] = vis;
403 }
404 
405 //! Insert a new view at index \a i.
insertView(int i,String active)406 void Page::insertView(int i, String active)
407 {
408   iViews.insert(iViews.begin() + i, SView());
409   iViews[i].iActive = active;
410   iViews[i].iMarked = false;
411   for (int l = 0; l < countLayers(); ++l)
412     iLayers[l].iVisible.insert(iLayers[l].iVisible.begin() + i, false);
413 }
414 
415 //! Remove the view at index \a i.
removeView(int i)416 void Page::removeView(int i)
417 {
418   iViews.erase(iViews.begin() + i);
419   for (int l = 0; l < countLayers(); ++l)
420     iLayers[l].iVisible.erase(iLayers[l].iVisible.begin() + i);
421 }
422 
423 //! Remove all views of this page.
clearViews()424 void Page::clearViews()
425 {
426   iViews.clear();
427   for (LayerSeq::iterator it = iLayers.begin();
428        it != iLayers.end(); ++it)
429     it->iVisible.clear();
430 }
431 
setMarkedView(int index,bool marked)432 void Page::setMarkedView(int index, bool marked)
433 {
434   iViews[index].iMarked = marked;
435 }
436 
countMarkedViews() const437 int Page::countMarkedViews() const
438 {
439   int count = 0;
440   for (int i = 0; i < countViews(); ++i) {
441     if (markedView(i))
442       ++count;
443   }
444   return (count == 0) ? 1 : count;
445 }
446 
447 //! Return index of view with given number or name.
448 /*! Input numbers are one-based.
449   Returns -1 if no such view exists.
450 */
findView(String s) const451 int Page::findView(String s) const
452 {
453   if (s.empty())
454     return -1;
455   if ('0' <= s[0] && s[0] <= '9') {
456     int no = Lex(s).getInt();
457     if (no <= 0 || no > countViews())
458       return -1;
459     return no - 1;
460   }
461   for (int i = 0; i < countViews(); ++i) {
462     if (s == viewName(i))
463       return i;
464     }
465   return -1;
466 }
467 
468 //! Return matrices for all layers.
layerMatrices(int view) const469 std::vector<Matrix> Page::layerMatrices(int view) const
470 {
471   std::vector<Matrix> m(countLayers());
472   for (int i = 0; i < size(iViews[view].iLayerMatrices); ++i) {
473     int l = findLayer(iViews[view].iLayerMatrices[i].iLayer);
474     if (l >= 0)
475       m[l] = iViews[view].iLayerMatrices[i].iMatrix;
476   }
477   return m;
478 }
479 
480 //! Set matrix for the given layer in this view.
setLayerMatrix(int view,int layer,const Matrix & m)481 void Page::setLayerMatrix(int view, int layer, const Matrix &m)
482 {
483   String name = iLayers[layer].iName;
484   std::vector<SLayerMatrix> &ms = iViews[view].iLayerMatrices;
485   std::vector<SLayerMatrix>::iterator it =
486     std::find_if(ms.begin(), ms.end(), [&name](const SLayerMatrix &a) {return a.iLayer == name;});
487   if (m.isIdentity()) {
488     if (it != ms.end()) ms.erase(it);
489   } else {
490     if (it != ms.end())
491       it->iMatrix = m;
492     else
493       ms.push_back({name, m});
494   }
495 }
496 
497 //! Set the attribute mapping for the view.
setViewMap(int index,const AttributeMap & map)498 void Page::setViewMap(int index, const AttributeMap &map)
499 {
500   iViews[index].iAttributeMap = map;
501 }
502 
503 // --------------------------------------------------------------------
504 
SObject()505 Page::SObject::SObject()
506 {
507   iObject = nullptr;
508   iLayer = 0;
509   iSelect = ENotSelected;
510 }
511 
SObject(const SObject & rhs)512 Page::SObject::SObject(const SObject &rhs)
513   : iSelect(rhs.iSelect), iLayer(rhs.iLayer)
514 {
515   if (rhs.iObject)
516     iObject = rhs.iObject->clone();
517   else
518     iObject = nullptr;
519 }
520 
operator =(const SObject & rhs)521 Page::SObject &Page::SObject::operator=(const SObject &rhs)
522 {
523   if (this != &rhs) {
524     delete iObject;
525     iSelect = rhs.iSelect;
526     iLayer = rhs.iLayer;
527     if (rhs.iObject)
528       iObject = rhs.iObject->clone();
529     else
530       iObject = nullptr;
531     iBBox.clear(); // invalidate
532   }
533   return *this;
534 }
535 
~SObject()536 Page::SObject::~SObject()
537 {
538   delete iObject;
539 }
540 
541 // --------------------------------------------------------------------
542 
543 //! Insert a new object at index \a i.
544 /*! Takes ownership of the object. */
insert(int i,TSelect select,int layer,Object * obj)545 void Page::insert(int i, TSelect select, int layer, Object *obj)
546 {
547   iObjects.insert(iObjects.begin() + i, SObject());
548   SObject &s = iObjects[i];
549   s.iSelect = select;
550   s.iLayer = layer;
551   s.iObject = obj;
552 }
553 
554 //! Append a new object.
555 /*! Takes ownership of the object. */
append(TSelect select,int layer,Object * obj)556 void Page::append(TSelect select, int layer, Object *obj)
557 {
558   iObjects.push_back(SObject());
559   SObject &s = iObjects.back();
560   s.iSelect = select;
561   s.iLayer = layer;
562   s.iObject = obj;
563 }
564 
565 //! Remove the object at index \a i.
remove(int i)566 void Page::remove(int i)
567 {
568   iObjects.erase(iObjects.begin() + i);
569 }
570 
571 //! Replace the object at index \a i.
572 /*! Takes ownership of \a obj. */
replace(int i,Object * obj)573 void Page::replace(int i, Object *obj)
574 {
575   delete iObjects[i].iObject;
576   iObjects[i].iObject = obj;
577   invalidateBBox(i);
578 }
579 
580 //! Return distance between object at index \a i and \a v.
581 /*! But may just return \a bound if the distance is larger.
582   This function uses the cached bounded box, and is faster than
583   calling Object::distance directly. */
distance(int i,const Vector & v,double bound) const584 double Page::distance(int i, const Vector &v, double bound) const
585 {
586   if (bbox(i).certainClearance(v, bound))
587     return bound;
588   return object(i)->distance(v, Matrix(), bound);
589 }
590 
591 //! Transform the object at index \a i.
592 /*! Use this function instead of calling Object::setMatrix directly,
593   as the latter doesn't invalidate the cached bounding box. */
transform(int i,const Matrix & m)594 void Page::transform(int i, const Matrix &m)
595 {
596   invalidateBBox(i);
597   object(i)->setMatrix(m * object(i)->matrix());
598 }
599 
600 //! Invalidate the bounding box at index \a i (the object is somehow changed).
invalidateBBox(int i) const601 void Page::invalidateBBox(int i) const
602 {
603   iObjects[i].iBBox.clear();
604 }
605 
606 //! Return a bounding box for the object at index \a i.
607 /*! This is a bounding box including the control points of the object.
608   If you need a tight bounding box, you'll need to use the Object
609   directly.
610 
611   The Page caches the box the first time it is computed.
612 
613   Make sure you call Page::transform instead of Object::setMatrix,
614   as the latter would not invalidate the bounding box.
615 */
616 
bbox(int i) const617 Rect Page::bbox(int i) const
618 {
619   if (iObjects[i].iBBox.isEmpty())
620     iObjects[i].iObject->addToBBox(iObjects[i].iBBox, Matrix(), true);
621   return iObjects[i].iBBox;
622 }
623 
624 //! Compute possible vertex snapping position for object at index \a i.
625 /*! Looks only for positions closer than \a bound.
626   If successful, modifies \a pos and \a bound. */
snapVtx(int i,const Vector & mouse,Vector & pos,double & bound) const627 void Page::snapVtx(int i, const Vector &mouse,
628 		   Vector &pos, double &bound) const
629 {
630   if (bbox(i).certainClearance(mouse, bound))
631     return;
632   object(i)->snapVtx(mouse, Matrix(), pos, bound);
633 }
634 
635 //! Compute possible control point snapping position for object at index \a i.
636 /*! Looks only for positions closer than \a bound.
637   If successful, modifies \a pos and \a bound. */
snapCtl(int i,const Vector & mouse,Vector & pos,double & bound) const638 void Page::snapCtl(int i, const Vector &mouse,
639 		   Vector &pos, double &bound) const
640 {
641   if (bbox(i).certainClearance(mouse, bound))
642     return;
643   object(i)->snapCtl(mouse, Matrix(), pos, bound);
644 }
645 
646 //! Compute possible boundary snapping position for object at index \a i.
647 /*! Looks only for positions closer than \a bound.
648   If successful, modifies \a pos and \a bound. */
snapBnd(int i,const Vector & mouse,Vector & pos,double & bound) const649 void Page::snapBnd(int i, const Vector &mouse,
650 		   Vector &pos, double &bound) const
651 {
652   if (bbox(i).certainClearance(mouse, bound))
653     return;
654   object(i)->snapBnd(mouse, Matrix(), pos, bound);
655 }
656 
657 //! Set attribute \a prop of object at index \a i to \a value.
658 /*! This method automatically invalidates the bounding box if a
659   ETextSize property is actually changed. */
setAttribute(int i,Property prop,Attribute value)660 bool Page::setAttribute(int i, Property prop, Attribute value)
661 {
662   bool changed = object(i)->setAttribute(prop, value);
663   if (changed && (prop == EPropTextSize || prop == EPropTransformations))
664     invalidateBBox(i);
665   return changed;
666 }
667 
668 // --------------------------------------------------------------------
669 
670 //! Return section title at \a level.
671 /*! Level 0 is the section, level 1 the subsection. */
section(int level) const672 String Page::section(int level) const
673 {
674   if (iUseTitle[level])
675     return title();
676   else
677     return iSection[level];
678 }
679 
680 //! Set the section title at \a level.
681 /*! Level 0 is the section, level 1 the subsection.
682 
683   If \a useTitle is \c true, then \a name is ignored, and the section
684   title will be copied from the page title (and further changes to the
685   page title are automatically reflected). */
setSection(int level,bool useTitle,String name)686 void Page::setSection(int level, bool useTitle, String name)
687 {
688   iUseTitle[level] = useTitle;
689   iSection[level] = useTitle ? String() : name;
690 }
691 
692 //! Set the title of this page.
693 /*! An empty title is not displayed. */
setTitle(String title)694 void Page::setTitle(String title)
695 {
696   iTitle = title;
697   iTitleObject.setText(String("\\PageTitle{") + title + "}");
698 }
699 
700 //! Return title of this page.
title() const701 String Page::title() const
702 {
703   return iTitle;
704 }
705 
706 //! Set the notes of this page.
setNotes(String notes)707 void Page::setNotes(String notes)
708 {
709   iNotes = notes;
710 }
711 
712 //! Set if page is marked for printing.
setMarked(bool marked)713 void Page::setMarked(bool marked)
714 {
715   iMarked = marked;
716 }
717 
718 //! Return Text object representing the title text.
719 /*! Return 0 if no title is set.
720   Ownership of object remains with Page.
721 */
titleText() const722 const Text *Page::titleText() const
723 {
724   if (title().empty())
725     return nullptr;
726   return &iTitleObject;
727 }
728 
729 //! Apply styling to title text object.
applyTitleStyle(const Cascade * sheet)730 void Page::applyTitleStyle(const Cascade *sheet)
731 {
732   if (title().empty())
733     return;
734   const StyleSheet::TitleStyle *ts = sheet->findTitleStyle();
735   if (!ts)
736     return;
737   iTitleObject.setMatrix(Matrix(ts->iPos));
738   iTitleObject.setSize(ts->iSize);
739   iTitleObject.setStroke(ts->iColor);
740   iTitleObject.setHorizontalAlignment(ts->iHorizontalAlignment);
741   iTitleObject.setVerticalAlignment(ts->iVerticalAlignment);
742 }
743 
744 // --------------------------------------------------------------------
745 
746 //! Return index of primary selection.
747 /*! Returns -1 if there is no primary selection. */
primarySelection() const748 int Page::primarySelection() const
749 {
750   for (int i = 0; i < count(); ++i)
751     if (select(i) == EPrimarySelected)
752       return i;
753   return -1;
754 }
755 
756 //! Returns true iff any object on the page is selected.
hasSelection() const757 bool Page::hasSelection() const
758 {
759   for (int i = 0; i < count(); ++i)
760     if (select(i))
761       return true;
762   return false;
763 }
764 
765 //! Deselect all objects.
deselectAll()766 void Page::deselectAll()
767 {
768   for (int i = 0; i < count(); ++i)
769     setSelect(i, ENotSelected);
770 }
771 
772 /*! If no object is the primary selection, make the topmost secondary
773   selection the primary one. */
ensurePrimarySelection()774 void Page::ensurePrimarySelection()
775 {
776   for (int i = 0; i < count(); ++i)
777     if (select(i) == EPrimarySelected)
778       return;
779   for (int i = count() - 1; i >= 0; --i) {
780     if (select(i) == ESecondarySelected) {
781       setSelect(i, EPrimarySelected);
782       return;
783     }
784   }
785 }
786 
787 //! Copy whole page with bitmaps as <ipepage> into the stream.
saveAsIpePage(Stream & stream) const788 void Page::saveAsIpePage(Stream &stream) const
789 {
790   BitmapFinder bmFinder;
791   bmFinder.scanPage(this);
792   stream << "<ipepage>\n";
793   int id = 1;
794   for (std::vector<Bitmap>::const_iterator it = bmFinder.iBitmaps.begin();
795        it != bmFinder.iBitmaps.end(); ++it) {
796     Bitmap bm = *it;
797     bm.saveAsXml(stream, id);
798     bm.setObjNum(id);
799     ++id;
800   }
801   saveAsXml(stream);
802   stream << "</ipepage>\n";
803 }
804 
805 //! Copy selected objects as <ipeselection> into the stream.
saveSelection(Stream & stream) const806 void Page::saveSelection(Stream &stream) const
807 {
808   BitmapFinder bmFinder;
809   for (int i = 0; i < count(); ++i) {
810     if (select(i))
811       object(i)->accept(bmFinder);
812   }
813   stream << "<ipeselection>\n";
814   int id = 1;
815   for (std::vector<Bitmap>::const_iterator it = bmFinder.iBitmaps.begin();
816        it != bmFinder.iBitmaps.end(); ++it) {
817     Bitmap bm = *it;
818     bm.saveAsXml(stream, id);
819     bm.setObjNum(id);
820     ++id;
821   }
822   for (int i = 0; i < count(); ++i) {
823     if (select(i))
824       object(i)->saveAsXml(stream, layer(layerOf(i)));
825   }
826   stream << "</ipeselection>\n";
827 }
828 
829 // --------------------------------------------------------------------
830