1
// --------------------------------------------------------------------
2
// A page of a document.
3
// --------------------------------------------------------------------
6
This file is part of the extensible drawing editor Ipe.
7
Copyright (C) 1993-2004 Otfried Cheong
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 2 of the License, or
12
(at your option) any later version.
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.
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.
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.
32
#include "ipevisitor.h"
37
#include "ipepainter.h"
41
// --------------------------------------------------------------------
45
\brief A layer of an IpePage.
47
The objects on one IpePage can belong to any number of layers.
48
Layers are orthogonal to the back-to-front ordering of objects, so a
49
"layer" is just another attribute of the object.
51
Layers have several attributes:
53
- They may be editable or locked. Objects in a locked layer cannot
54
be selected, and a locked layer cannot be made active in the Ipe
55
UI. This more or less means that the contents of such a layer
56
cannot be modified---but that's a consequence of the UI, Ipelib
57
contains no special handling of locked layers.
59
- A layer may be visible, invisible, or dimmed.
61
- A layer may have snapping on or off---objects will behave
62
magnetically only if their layer has snapping on.
64
The PDF output generated for an IpePage depends on its \e
65
views. Each view may list a number of layers to be displayed at that
66
stage. Multiple \e views may show different subsets of layers.
69
//! Construct with name. Default attributes.
70
IpeLayer::IpeLayer(IpeString name)
76
//! Construct from a single XML tag.
77
IpeLayer::IpeLayer(const IpeXmlAttributes &attr)
82
if (attr.Has("visible", str)) {
88
if (attr.Has("edit", str) && str == "no")
92
//! Write a single XML tag describing this layer.
93
void IpeLayer::SaveAsXml(IpeStream &stream) const
95
stream << "<layer name=\"" << iName << "\"";
96
switch (iFlags & (EHidden|EDim)) {
100
stream << " visible=\"dim\"";
103
stream << " visible=\"no\"";
106
if (iFlags & ELocked)
107
stream << " edit=\"no\"";
112
void IpeLayer::SetVisible(bool flag)
115
if (!flag) iFlags |= EHidden;
119
void IpeLayer::SetDimmed(bool flag)
122
if (flag) iFlags |= EDim;
126
void IpeLayer::SetLocked(bool flag)
129
if (flag) iFlags |= ELocked;
133
void IpeLayer::SetSnapping(bool flag)
135
iFlags &= ~ENoSnapping;
136
if (!flag) iFlags |= ENoSnapping;
139
// --------------------------------------------------------------------
143
\brief A view of the page (set of layers, duration, effect, transition style)
145
An IpePage contains a whole list of these.
148
//! Construct default view.
156
//! Create a view from XML tag.
157
IpeView::IpeView(const IpeXmlAttributes &attr)
162
IpeLex st(attr["layers"]);
164
iLayers.push_back(st.NextToken());
167
iActive = attr["active"];
169
if (attr.Has("duration", str))
170
iDuration = IpeLex(str).GetInt();
171
if (attr.Has("transition", str))
172
iTransitionTime = IpeLex(str).GetInt();
173
if (attr.Has("effect", str))
174
iEffect = TEffect(IpeLex(str).GetInt());
177
//! Write a single XML tag representing this view.
178
void IpeView::SaveAsXml(IpeStream &stream) const
180
stream << "<view layers=\"";
181
for (std::vector<IpeString>::const_iterator it = iLayers.begin();
182
it != iLayers.end(); ++it) {
183
if (it != iLayers.begin())
188
if (!iActive.empty())
189
stream << " active=\"" << iActive << "\"";
191
stream << " duration=\"" << iDuration << "\"";
192
if (iEffect != ENormal)
193
stream << " effect=\"" << int(iEffect) << "\"";
194
if (iTransitionTime > 1)
195
stream << " transition=\"" << iTransitionTime << "\"";
200
//! Write part of page dictionary.
201
/*! Write part of page dictionary indicating effect,
202
including the two keys /Dur and /Trans. */
203
void IpeView::PageDictionary(IpeStream &stream) const
206
stream << "/Dur " << iDuration << "\n";
207
if (iEffect != ENormal) {
208
stream << "/Trans << /D " << iTransitionTime << " /S ";
210
case ESplitHI: stream << "/Split /Dm /H /M /I"; break;
211
case ESplitHO: stream << "/Split /Dm /H /M /O"; break;
212
case ESplitVI: stream << "/Split /Dm /V /M /I"; break;
213
case ESplitVO: stream << "/Split /Dm /V /M /O"; break;
214
case EBlindsH: stream << "/Blinds /Dm /H"; break;
215
case EBlindsV: stream << "/Blinds /Dm /V"; break;
216
case EBoxI: stream << "/Box /M /I"; break;
217
case EBoxO: stream << "/Box /M /O"; break;
218
case EWipeLR: stream << "/Wipe /Di 0"; break;
219
case EWipeBT: stream << "/Wipe /Di 90"; break;
220
case EWipeRL: stream << "/Wipe /Di 180"; break;
221
case EWipeTB: stream << "/Wipe /Di 270"; break;
222
case EDissolve: stream << "/Dissolve"; break;
223
case EGlitterLR: stream << "/Glitter /Di 0"; break;
224
case EGlitterTB: stream << "/Glitter /Di 270"; break;
225
case EGlitterD: stream << "/Glitter /Di 315"; break;
226
case ENormal: break; // to satisfy compiler
232
// --------------------------------------------------------------------
236
\brief An Ipe document page.
238
Its main ingredients are a sequence of IpePgObjects, a list of
239
IpeLayers, and a list of IpeViews.
241
If you need to keep track of whether a document has been modified,
242
you have to call SetEdited(true) whenever you modify an IpePgObject.
244
The functions to modify the layer sequence and the views set the
245
edited flag themselves.
248
//! The default constructor creates a new empty page.
255
// --------------------------------------------------------------------
257
//! Save page in XML format.
258
void IpePage::SaveAsXml(IpePainter &painter, IpeStream &stream) const
262
stream << " gridsize=\"" << iGridSize << "\"";
264
for (int i = 0; i < CountLayers(); ++i) {
265
Layer(i).SaveAsXml(stream);
267
for (IpeViewSeq::const_iterator it = iViews.begin();
268
it != iViews.end(); ++it) {
269
it->SaveAsXml(stream);
271
int currentLayer = -1;
272
for (const_iterator it = begin(); it != end(); ++it) {
274
if (it->Layer() != currentLayer) {
275
currentLayer = it->Layer();
276
layer = Layer(currentLayer).iName;
278
it->Object()->SaveAsXml(painter, stream, layer);
280
stream << "</page>\n";
284
/*! Sets the edited flag. */
285
void IpePage::SetLayer(int index, const IpeLayer &layer)
287
iLayers[index] = layer;
291
//! Add a new layer at \a index (at the end if \a index is negative).
292
/*! Returns index of new layer, and sets edited flag.
293
Layer numbers of all objects on page are adjusted if necessary.
295
int IpePage::AddLayer(const IpeLayer &layer, int index)
299
iLayers.push_back(layer);
300
return (iLayers.size() - 1);
302
IpeLayerSeq::iterator it = iLayers.begin() + index;
303
iLayers.insert(it, layer);
304
// adjust all objects!
305
for (iterator pit = begin(); pit != end(); ++pit) {
306
int l = pit->Layer();
308
pit->SetLayer(l + 1);
314
//! Find layer with given name.
315
/*! Returns -1 if not found. */
316
int IpePage::FindLayer(IpeString name) const
318
for (int i = 0; i < CountLayers(); ++i)
319
if (Layer(i).Name() == name)
324
const char * const layerNames[] = {
325
"alpha", "beta", "gamma", "delta", "epsilon", "zeta", "eta",
326
"theta", "iota", "kappa", "lambda", "mu", "nu", "xi",
327
"omicron", "pi", "rho", "sigma", "tau", "phi", "chi", "xi",
330
//! Create a new layer with unique name.
331
/*! The layer is inserted at index \a index, or appended if
332
\a index is negative.
333
Returns index of new layer. */
334
int IpePage::NewLayer(int index)
337
for (int i = 0; i < int(sizeof(layerNames)/sizeof(const char *)); ++i) {
338
if (FindLayer(layerNames[i]) < 0)
339
return AddLayer(IpeLayer(layerNames[i]), index);
344
std::sprintf(name, "alpha%d", i);
345
if (FindLayer(name) < 0)
346
return AddLayer(IpeLayer(name), index);
351
//! Deletes an empty layer from the page.
352
/*! All objects are adjusted. Panics if there are objects in the
353
deleted layer, of if it is the only layer.
354
The layer is also removed from all views.
356
void IpePage::DeleteLayer(int index)
358
assert(iLayers.size() > 1);
359
for (iterator it = begin(); it != end(); ++it) {
366
// remove from all views
367
IpeString name = iLayers[index].Name();
368
for (int i = 0; i < CountViews(); ++i) {
369
IpeView &view = iViews[i];
370
for (std::vector<IpeString>::iterator it = view.iLayers.begin();
371
it != view.iLayers.end(); ++it) {
373
view.iLayers.erase(it);
379
iLayers.erase(iLayers.begin() + index);
383
//! Does a view exist where this layer is active?
384
bool IpePage::IsLayerActiveInView(int index) const
386
IpeString name = iLayers[index].Name();
387
for (int i = 0; i < CountViews(); ++i)
388
if (iViews[i].iActive == name)
393
//! Set the views for the page.
394
/*! This sets the edited flag. */
395
void IpePage::SetViews(const IpeViewSeq &views)
401
//! Sets one view of the page.
402
/*! This sets the edited flag. */
403
void IpePage::SetView(int index, const IpeView &view)
405
iViews[index] = view;
409
//! Add a view at position \a index.
410
/*! The view is appended at the end if \a index is negative. */
411
void IpePage::AddView(const IpeView &view, int index)
414
iViews.push_back(view);
416
iViews.insert(iViews.begin() + index, view);
420
//! Delete view at \a index.
421
void IpePage::DeleteView(int index)
423
iViews.erase(iViews.begin() + index);
427
class TextBoxVisitor : public IpeVisitor {
429
virtual void VisitText(const IpeText *obj);
435
void TextBoxVisitor::VisitText(const IpeText *t)
437
// look at all minipage objects that span the same horizontal extent
438
if (t->Type() == IpeText::ETextbox) {
439
IpeScalar bottom = (t->Matrix() * t->Position()).iY - t->TotalHeight();
445
//! Computes text box.
446
/*! Takes into account media size, margins, and text objects already
448
IpeRect IpePage::TextBox(const IpeRect &media,
449
const IpeStyleSheet *sheet) const
452
sheet->FindMargins(tl, br);
454
vis.iR = IpeRect(media.Min() + IpeVector(tl.iX, br.iY),
455
media.Max() - IpeVector(br.iX, tl.iY));
456
vis.iY = vis.iR.Max().iY;
457
for (const_iterator it = begin(); it != end(); ++it)
459
return IpeRect(vis.iR.Min(), IpeVector(vis.iR.Max().iX, vis.iY));
462
//! Set whether page has been edited.
463
void IpePage::SetEdited(bool edited)
468
//! Returns true iff any object on the page is selected.
469
bool IpePage::HasSelection() const
471
for (const_iterator it = begin(); it != end(); ++it) {
472
if (it->Select() != IpePgObject::ENone)
478
//! Returns the primary selection, or end().
479
IpePage::iterator IpePage::PrimarySelection()
481
for (iterator it = begin(); it != end(); ++it) {
482
if (it->Select() == IpePgObject::EPrimary)
488
//! Deselect all objects.
489
void IpePage::DeselectAll()
491
for (iterator it = begin(); it != end(); ++it)
492
it->SetSelect(IpePgObject::ENone);
495
//! Deselect all objects in this layer.
496
void IpePage::DeselectLayer(int layer)
498
for (iterator it = begin(); it != end(); ++it)
499
if (it->Layer() == layer)
500
it->SetSelect(IpePgObject::ENone);
503
//! Deselect all objects not in a layer of this view.
504
void IpePage::DeselectNotInView(int view)
506
if (CountViews() > 0) {
507
// make a table first
508
const std::vector<IpeString> &layerNames = iViews[view].iLayers;
509
std::vector<bool> layer;
510
for (int i = 0; i < CountLayers(); ++i) {
512
= (std::find(layerNames.begin(), layerNames.end(), iLayers[i].Name())
513
!= layerNames.end());
514
layer.push_back(inView);
516
// now deselect all objects
517
for (iterator it = begin(); it != end(); ++it)
518
if (!layer[it->Layer()])
519
it->SetSelect(IpePgObject::ENone);
523
/*! If no object is the primary selection, make the topmost secondary
524
selection the primary one. */
525
void IpePage::EnsurePrimarySelection()
527
for (IpePage::const_iterator it = begin(); it != end(); ++it) {
528
if (it->Select() == IpePgObject::EPrimary)
531
IpePage::reverse_iterator it = rbegin();
532
while (it != rend() && it->Select() != IpePgObject::ESecondary)
535
it->SetSelect(IpePgObject::EPrimary);
538
//! Removes all selected objects from the page.
539
/*! They are added, in the same order, to \a seq. */
540
void IpePage::ExtractSelection(IpePgObjectSeq &seq)
543
for (iterator it = begin(); it != end(); it = it1) {
546
if (it->Select() != IpePgObject::ENone) {
554
//! Delete currently selected objects
555
void IpePage::Delete()
558
for (iterator it = begin(); it != end(); it = it1) {
561
if (it->Select() != IpePgObject::ENone) {
568
//! Select all objects on the page.
569
void IpePage::SelectAll()
571
for (iterator it = begin(); it != end(); ++it) {
573
it->SetSelect(IpePgObject::ESecondary);
575
EnsurePrimarySelection();
578
//! Select all objects in the given layer.
579
void IpePage::SelectAllInLayer(int layer)
582
IpePgObject::TSelect sel = IpePgObject::EPrimary;
583
for (iterator it = begin(); it != end(); ++it) {
584
if (it->Layer() == layer) {
586
sel = IpePgObject::ESecondary;
591
//! Group the selected objects together as a new group object.
592
/*! The new object is placed in \a layer. */
593
void IpePage::Group(int layer)
596
ExtractSelection(objs);
597
IpeGroup *group = new IpeGroup;
598
for (IpePgObjectSeq::iterator it = objs.begin(); it != objs.end(); ++it)
599
group->push_back(it->Object()->Clone());
600
push_back(IpePgObject(IpePgObject::EPrimary, layer, group));
604
//! Move selected objects to indicated layer.
605
void IpePage::MoveToLayer(int layer)
607
for (iterator it = begin(); it != end(); ++it) {
614
//! Ungroup the primary selection, place objects in \a layer.
615
/*! Panics if no primary selection, returns false if primary selection
617
bool IpePage::Ungroup(int layer)
619
iterator it = PrimarySelection();
620
const IpeGroup *group = it->Object()->AsGroup();
623
for (IpeGroup::const_iterator it1 = group->begin();
624
it1 != group->end(); ++it1) {
625
IpeObject *obj = (*it1)->Clone();
626
// apply attributes from group
628
obj->SetStroke(group->Stroke());
629
obj->SetMatrix(group->Matrix() * obj->Matrix());
630
IpeFillable *fobj = obj->AsFillable();
633
fobj->SetFill(group->Fill());
634
if (group->LineWidth())
635
fobj->SetLineWidth(group->LineWidth());
636
if (group->DashStyle())
637
fobj->SetDashStyle(group->DashStyle());
639
if (group->TextSize()) {
641
obj->AsText()->SetSize(group->TextSize());
642
else if (obj->AsGroup())
643
obj->AsGroup()->SetTextSize(group->TextSize());
645
if (group->MarkSize()) {
647
obj->AsMark()->SetSize(group->MarkSize());
648
else if (obj->AsGroup())
649
obj->AsGroup()->SetMarkSize(group->MarkSize());
651
if (group->MarkShape()) {
653
obj->AsMark()->SetShape(group->MarkShape());
654
else if (obj->AsGroup())
655
obj->AsGroup()->SetMarkShape(group->MarkShape());
657
push_back(IpePgObject(IpePgObject::ESecondary, layer, obj));
661
EnsurePrimarySelection();
665
//! Move selected objects to front.
666
void IpePage::Front()
669
ExtractSelection(objs);
670
for (IpePgObjectSeq::iterator it = objs.begin();
671
it != objs.end(); ++it) {
677
//! Move selected objects to back.
681
ExtractSelection(objs);
682
iterator it1 = begin();
683
if (Layer(0).Name() == "background") {
684
// skip all objects in this layer
685
while (it1 != end() && it1->Layer() == 0)
688
for (IpePgObjectSeq::reverse_iterator it = objs.rbegin();
689
it != objs.rend(); ++it) {
696
//! Duplicate the selected objects into \a layer.
697
void IpePage::Duplicate(int layer)
699
// remember number of elements on page
700
iterator it = begin();
704
push_back(IpePgObject(IpePgObject::ESecondary, layer,
705
it->Object()->Clone()));
706
it->SetSelect(IpePgObject::ENone); // unselect old
711
EnsurePrimarySelection();
714
//! Set stroke color of selected objects.
715
void IpePage::SetStroke(IpeAttribute color)
717
for (iterator it = begin(); it != end(); ++it) {
718
if (it->Select() != IpePgObject::ENone) {
719
it->Object()->SetStroke(color);
725
//! Set fill color of selected objects.
726
void IpePage::SetFill(IpeAttribute color)
728
for (iterator it = begin(); it != end(); ++it) {
729
if (it->Select() != IpePgObject::ENone) {
730
IpeFillable *obj = it->Object()->AsFillable();
739
//! Set line width of selected objects.
740
void IpePage::SetLineWidth(IpeAttribute attr)
742
for (iterator it = begin(); it != end(); ++it) {
743
if (it->Select() != IpePgObject::ENone) {
744
IpeFillable *obj = it->Object()->AsFillable();
746
obj->SetLineWidth(attr);
753
//! Set line style of selected objects.
754
void IpePage::SetDashStyle(IpeAttribute attr)
756
for (iterator it = begin(); it != end(); ++it) {
757
if (it->Select() != IpePgObject::ENone) {
758
IpeFillable *obj = it->Object()->AsFillable();
760
obj->SetDashStyle(attr);
767
//! Set arrows of selected objects.
768
void IpePage::SetArrows(bool forward, bool backward, IpeAttribute size)
770
for (iterator it = begin(); it != end(); ++it) {
771
if (it->Select() != IpePgObject::ENone) {
772
IpePath *path = it->Object()->AsPath();
775
path->SetForwardArrow(size);
777
path->SetForwardArrow(IpeAttribute());
779
path->SetBackwardArrow(size);
781
path->SetBackwardArrow(IpeAttribute());
788
//! Set arrow size of selected objects.
789
void IpePage::SetArrowSize(IpeAttribute size)
791
for (iterator it = begin(); it != end(); ++it) {
792
if (it->Select() != IpePgObject::ENone) {
793
IpePath *path = it->Object()->AsPath();
795
if (!path->ForwardArrow().IsNull())
796
path->SetForwardArrow(size);
797
if (!path->BackwardArrow().IsNull())
798
path->SetBackwardArrow(size);
805
//! Set text size of selected objects.
806
void IpePage::SetTextSize(IpeAttribute size)
808
for (iterator it = begin(); it != end(); ++it) {
809
if (it->Select() != IpePgObject::ENone) {
810
IpeText *obj = it->Object()->AsText();
813
it->InvalidateBBox();
814
obj->SetXForm(0); // XForm is no longer useful
817
IpeGroup *grp = it->Object()->AsGroup();
819
grp->SetTextSize(size);
827
//! Set mark size of selected objects.
828
void IpePage::SetMarkSize(IpeAttribute size)
830
for (iterator it = begin(); it != end(); ++it) {
831
if (it->Select() != IpePgObject::ENone) {
832
IpeMark *obj = it->Object()->AsMark();
837
IpeGroup *grp = it->Object()->AsGroup();
839
grp->SetMarkSize(size);
847
//! Set mark shape of selected objects.
848
void IpePage::SetMarkShape(int shape)
850
for (iterator it = begin(); it != end(); ++it) {
851
if (it->Select() != IpePgObject::ENone) {
852
IpeMark *obj = it->Object()->AsMark();
854
obj->SetShape(shape);
857
IpeGroup *grp = it->Object()->AsGroup();
859
grp->SetMarkShape(shape);
867
//! Copy selected objects into the stream.
868
void IpePage::Copy(IpeStream &stream, const IpeStyleSheet *sheet) const
870
IpeBitmapFinder bmFinder;
871
for (const_iterator it = begin(); it != end(); ++it) {
872
if (it->Select() != IpePgObject::ENone)
873
it->Object()->Accept(bmFinder);
875
stream << "<ipeselection>\n";
877
for (std::vector<IpeBitmap>::const_iterator it = bmFinder.iBitmaps.begin();
878
it != bmFinder.iBitmaps.end(); ++it) {
879
it->SaveAsXml(stream, id);
883
IpePainter painter(sheet);
884
for (const_iterator it = begin(); it != end(); ++it) {
885
if (it->Select() != IpePgObject::ENone) {
886
it->Object()->SaveAsXml(painter, stream, IpeString());
889
stream << "</ipeselection>\n";
892
//! Copy whole page into the stream.
893
void IpePage::CopyPage(IpeStream &stream, const IpeStyleSheet *sheet) const
895
IpeBitmapFinder bmFinder;
896
bmFinder.ScanPage(this);
897
stream << "<ipepage>\n";
899
for (std::vector<IpeBitmap>::const_iterator it = bmFinder.iBitmaps.begin();
900
it != bmFinder.iBitmaps.end(); ++it) {
902
bm.SaveAsXml(stream, id);
906
IpePainter painter(sheet);
907
SaveAsXml(painter, stream);
908
stream << "</ipepage>\n";
911
//! Paste objects from XML source into \a layer.
912
/*! Returns false if XML source cannot be parsed. */
913
bool IpePage::Paste(int layer, IpeXmlDataSource &source,
916
IpeImlParser parser(source, rep);
918
if (!parser.ParseSelection(seq) || seq.empty())
922
IpePgObject::TSelect sel = IpePgObject::EPrimary;
923
for (IpePgObjectSeq::const_iterator it = seq.begin();
924
it != seq.end(); ++it) {
925
push_back(IpePgObject(sel, layer, it->Object()->Clone()));
926
sel = IpePgObject::ESecondary;
932
//! If no selected object is close, select closest object.
933
/*! If there is a selected object at distance at most \a d from \a
934
pos, return true. Otherwise, check whether the closest object to \a
935
pos has distance at most \a d. If so, unselect everything, make
936
this object the primary selection, and return true. If not, return
937
whether the page has a selection at all.
939
If \a primaryOnly is \c true, the primary selection has to be at
940
distance at most \a d, otherwise it'll be replaced as above.
942
bool IpePage::UpdateCloseSelection(const IpeVector &pos, double d,
945
// check whether a selected object is close enough
947
iterator it = PrimarySelection();
948
if (it != end() && it->Distance(pos, d) < d)
951
for (iterator it = begin(); it != end(); ++it) {
952
if (it->Select() != IpePgObject::ENone &&
953
it->Distance(pos, d) < d)
958
// no --- find closest object
960
iterator it1 = end();
961
for (iterator it = begin(); it != end(); ++it) {
962
if (Layer(it->Layer()).IsVisible() &&
963
!Layer(it->Layer()).IsLocked()) {
964
if ((d1 = it->Distance(pos, d)) < d) {
971
// closest object close enough?
973
// deselect all, and select it
974
for (iterator it = begin(); it != end(); ++it)
975
it->SetSelect(IpePgObject::ENone);
976
it1->SetSelect(IpePgObject::EPrimary);
979
return HasSelection();
982
// --------------------------------------------------------------------
984
//! Create one path object with all the subpaths from the selection.
985
/*! The new object takes the attributes from the primary selection,
986
and is placed in \a layer.
987
The function returns false if non-path objects are selected. */
988
bool IpePage::ComposePaths(int layer)
990
for (iterator it = begin(); it != end(); ++it) {
991
if (it->Select() && !it->Object()->AsPath())
994
IpePath *prim = PrimarySelection()->Object()->AsPath();
995
IpeAllAttributes attr;
996
attr.iStroke = prim->Stroke();
997
attr.iFill = prim->Fill();
998
attr.iLineWidth = prim->LineWidth();
999
attr.iDashStyle = prim->DashStyle();
1000
attr.iForwardArrow = false;
1001
attr.iBackwardArrow = false;
1002
IpePath *obj = new IpePath(attr);
1005
for (iterator it = begin(); it != end(); it = it1) {
1009
IpePath *p = it->Object()->AsPath();
1010
for (int i = 0; i < p->NumSubPaths(); ++i)
1011
obj->AddSubPath(p->SubPath(i)->Transform(p->Matrix()));
1015
push_back(IpePgObject(IpePgObject::EPrimary, layer, obj));
1020
//! Decompose one path object into separate objects for the subpaths.
1021
/*! The new objects are placed in \a layer.
1022
The function returns false if the primary selection is not a path object. */
1023
bool IpePage::DecomposePath(int layer)
1025
IpePath *prim = PrimarySelection()->Object()->AsPath();
1028
IpeAllAttributes attr;
1029
attr.iStroke = prim->Stroke();
1030
attr.iFill = prim->Fill();
1031
attr.iLineWidth = prim->LineWidth();
1032
attr.iDashStyle = prim->DashStyle();
1033
attr.iForwardArrow = false;
1034
attr.iBackwardArrow = false;
1035
IpeMatrix tfm = prim->Matrix();
1037
for (int i = 0; i < prim->NumSubPaths(); ++i) {
1038
IpeSubPath *sp = prim->SubPath(i)->Clone();
1039
IpePath *obj = new IpePath(attr);
1040
obj->SetMatrix(tfm);
1041
obj->AddSubPath(sp);
1042
push_back(IpePgObject(IpePgObject::ESecondary, layer, obj));
1044
erase(PrimarySelection());
1045
EnsurePrimarySelection();
1050
// --------------------------------------------------------------------
1052
const double joinThreshold = 0.000001;
1056
const IpeSegmentSubPath *iPath; // not owned
1060
static int FindPartner(const IpeVector &v, SSubPath *subs, int beg, int end)
1062
while (beg != end) {
1063
if ((v - subs[beg].iV[0]).SqLen() < joinThreshold)
1065
if ((v - subs[beg].iV[1]).SqLen() < joinThreshold) {
1066
IpeVector t = subs[beg].iV[0];
1067
subs[beg].iV[0] = subs[beg].iV[1];
1068
subs[beg].iV[1] = t;
1069
subs[beg].iFlip = true;
1077
IpePath *IpePage::DoJoinPaths(IpePath *prim, SSubPath *subs, int size)
1080
((subs[0].iV[0] - subs[size-1].iV[1]).SqLen() < joinThreshold);
1082
IpeAllAttributes attr;
1083
attr.iStroke = prim->Stroke();
1084
attr.iFill = prim->Fill();
1085
attr.iLineWidth = prim->LineWidth();
1086
attr.iDashStyle = prim->DashStyle();
1087
attr.iForwardArrow = false;
1088
attr.iBackwardArrow = false;
1090
IpeSegmentSubPath *sp = new IpeSegmentSubPath;
1092
for (int i = 0; i < size; ++i) {
1093
const IpeSegmentSubPath *p = subs[i].iPath;
1094
if (subs[i].iFlip) {
1095
for (int j = p->NumSegments() - 1; j >= 0; --j)
1096
sp->AppendReversed(p->Segment(j));
1098
for (int j = 0; j < p->NumSegments(); ++j)
1099
sp->Append(p->Segment(j));
1103
IpePath *obj = new IpePath(attr);
1104
sp->SetClosed(closePath);
1105
obj->AddSubPath(sp);
1109
//! Join paths into one long path.
1110
/*! Create one path object with the open subpaths from the selection
1111
joined into one long subpath.
1112
The new object takes the attributes from the primary selection,
1113
and is placed in \a layer.
1114
The function returns \c false if objects are selected that do not consist
1115
of open subpaths only.
1117
bool IpePage::JoinPaths(int layer)
1119
std::vector<SSubPath> subs;
1120
for (iterator it = begin(); it != end(); ++it) {
1122
IpePath *p = it->Object()->AsPath();
1123
// must be a single open subpath
1124
if (!p || p->NumSubPaths() > 1 || p->SubPath(0)->Closed())
1126
const IpeSegmentSubPath *sp = p->SubPath(0)->AsSegs();
1129
sub.iV[0] = sp->Segment(0).CP(0);
1130
IpePathSegment back = sp->Segment(-1);
1131
sub.iV[1] = back.CP(back.NumCP() - 1);
1133
subs.push_back(sub);
1136
// match up endpoints
1137
if (FindPartner(subs[0].iV[1], &subs[0], 1, subs.size()) < 0) {
1138
// not found, flip subs[0] and try again
1139
IpeVector t = subs[0].iV[0];
1140
subs[0].iV[0] = subs[0].iV[1];
1142
subs[0].iFlip = true;
1144
for (int i = 0; i < int(subs.size()) - 2; ++i) {
1145
// invariant: subs[0] .. subs[i] form a chain
1146
int j = FindPartner(subs[i].iV[1], &subs[0], i+1, subs.size());
1148
return false; // no match
1151
SSubPath sub = subs[i+1];
1152
subs[i+1] = subs[j];
1156
IpePath *prim = PrimarySelection()->Object()->AsPath();
1157
IpePath *obj = DoJoinPaths(prim, &subs[0], subs.size());
1158
// erase the selected objects
1160
push_back(IpePgObject(IpePgObject::EPrimary, layer, obj));
1165
// --------------------------------------------------------------------