summaryrefslogtreecommitdiffstats
path: root/src/livarot
diff options
context:
space:
mode:
Diffstat (limited to 'src/livarot')
-rw-r--r--src/livarot/AVL.cpp969
-rw-r--r--src/livarot/AVL.h104
-rw-r--r--src/livarot/AlphaLigne.cpp308
-rw-r--r--src/livarot/AlphaLigne.h87
-rw-r--r--src/livarot/BitLigne.cpp180
-rw-r--r--src/livarot/BitLigne.h64
-rw-r--r--src/livarot/CMakeLists.txt44
-rw-r--r--src/livarot/LivarotDefs.h95
-rw-r--r--src/livarot/Path.cpp945
-rw-r--r--src/livarot/Path.h1001
-rw-r--r--src/livarot/PathConversion.cpp1638
-rw-r--r--src/livarot/PathCutting.cpp1529
-rw-r--r--src/livarot/PathOutline.cpp1530
-rw-r--r--src/livarot/PathSimplify.cpp1550
-rw-r--r--src/livarot/PathStroke.cpp763
-rw-r--r--src/livarot/README17
-rw-r--r--src/livarot/Shape.cpp2347
-rw-r--r--src/livarot/Shape.h1202
-rw-r--r--src/livarot/ShapeDraw.cpp114
-rw-r--r--src/livarot/ShapeMisc.cpp1476
-rw-r--r--src/livarot/ShapeRaster.cpp2014
-rw-r--r--src/livarot/ShapeSweep.cpp3637
-rw-r--r--src/livarot/float-line.cpp916
-rw-r--r--src/livarot/float-line.h144
-rw-r--r--src/livarot/int-line.cpp1071
-rw-r--r--src/livarot/int-line.h119
-rw-r--r--src/livarot/path-description.cpp146
-rw-r--r--src/livarot/path-description.h262
-rw-r--r--src/livarot/sweep-event-queue.h108
-rw-r--r--src/livarot/sweep-event.cpp284
-rw-r--r--src/livarot/sweep-event.h72
-rw-r--r--src/livarot/sweep-tree-list.cpp56
-rw-r--r--src/livarot/sweep-tree-list.h84
-rw-r--r--src/livarot/sweep-tree.cpp614
-rw-r--r--src/livarot/sweep-tree.h326
35 files changed, 25816 insertions, 0 deletions
diff --git a/src/livarot/AVL.cpp b/src/livarot/AVL.cpp
new file mode 100644
index 0000000..9bf6eb4
--- /dev/null
+++ b/src/livarot/AVL.cpp
@@ -0,0 +1,969 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * TODO: insert short description here
+ *//*
+ * Authors:
+ * see git history
+ * Fred
+ *
+ * Copyright (C) 2018 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "AVL.h"
+
+/*
+ * the algorithm explanation for this code comes from purists.org, which seems to have disappeared since
+ * it's a classic AVL tree rebalancing, nothing fancy
+ */
+
+AVLTree::AVLTree()
+{
+ MakeNew();
+}
+
+AVLTree::~AVLTree()
+{
+ MakeDelete();
+}
+
+void AVLTree::MakeNew()
+{
+ for (int i = 0; i < 2; i++)
+ {
+ elem[i] = nullptr;
+ child[i] = nullptr;
+ }
+
+ parent = nullptr;
+ balance = 0;
+}
+
+void AVLTree::MakeDelete()
+{
+ for (int i = 0; i < 2; i++) {
+ if (elem[i]) {
+ elem[i]->elem[1 - i] = elem[1 - i];
+ }
+ elem[i] = nullptr;
+ }
+}
+
+AVLTree *AVLTree::Leftmost()
+{
+ return leafFromParent(nullptr, LEFT);
+}
+
+AVLTree *AVLTree::leaf(AVLTree *from, Side s)
+{
+ if (from == child[1 - s]) {
+ if (child[s]) {
+ return child[s]->leafFromParent(this, s);
+ }
+ else if (parent) {
+ return parent->leaf(this, s);
+ }
+ }
+ else if (from == child[s]) {
+ if (parent) {
+ return parent->leaf(this, s);
+ }
+ }
+
+ return nullptr;
+}
+
+AVLTree *AVLTree::leafFromParent(AVLTree */*from*/, Side s)
+{
+ if (child[s]) {
+ return child[s]->leafFromParent(this, s);
+ }
+
+ return this;
+}
+
+int
+AVLTree::RestoreBalances (AVLTree * from, AVLTree * &racine)
+{
+ if (from == nullptr)
+ {
+ if (parent)
+ return parent->RestoreBalances (this, racine);
+ }
+ else
+ {
+ if (balance == 0)
+ {
+ if (from == child[LEFT])
+ balance = 1;
+ if (from == child[RIGHT])
+ balance = -1;
+ if (parent)
+ return parent->RestoreBalances (this, racine);
+ return avl_no_err;
+ }
+ else if (balance > 0)
+ {
+ if (from == child[RIGHT])
+ {
+ balance = 0;
+ return avl_no_err;
+ }
+ if (child[LEFT] == nullptr)
+ {
+// cout << "mierda\n";
+ return avl_bal_err;
+ }
+ AVLTree *a = this;
+ AVLTree *b = child[LEFT];
+ AVLTree *e = child[RIGHT];
+ AVLTree *c = child[LEFT]->child[LEFT];
+ AVLTree *d = child[LEFT]->child[RIGHT];
+ if (child[LEFT]->balance > 0)
+ {
+ AVLTree *r = parent;
+
+ a->parent = b;
+ b->child[RIGHT] = a;
+ a->child[RIGHT] = e;
+ if (e)
+ e->parent = a;
+ a->child[LEFT] = d;
+ if (d)
+ d->parent = a;
+ b->child[LEFT] = c;
+ if (c)
+ c->parent = b;
+ b->parent = r;
+ if (r)
+ {
+ if (r->child[LEFT] == a)
+ r->child[LEFT] = b;
+ if (r->child[RIGHT] == a)
+ r->child[RIGHT] = b;
+ }
+ if (racine == a)
+ racine = b;
+
+ a->balance = 0;
+ b->balance = 0;
+ return avl_no_err;
+ }
+ else
+ {
+ if (child[LEFT]->child[RIGHT] == nullptr)
+ {
+ // cout << "mierda\n";
+ return avl_bal_err;
+ }
+ AVLTree *f = child[LEFT]->child[RIGHT]->child[LEFT];
+ AVLTree *g = child[LEFT]->child[RIGHT]->child[RIGHT];
+ AVLTree *r = parent;
+
+ a->parent = d;
+ d->child[RIGHT] = a;
+ b->parent = d;
+ d->child[LEFT] = b;
+ a->child[LEFT] = g;
+ if (g)
+ g->parent = a;
+ a->child[RIGHT] = e;
+ if (e)
+ e->parent = a;
+ b->child[LEFT] = c;
+ if (c)
+ c->parent = b;
+ b->child[RIGHT] = f;
+ if (f)
+ f->parent = b;
+
+ d->parent = r;
+ if (r)
+ {
+ if (r->child[LEFT] == a)
+ r->child[LEFT] = d;
+ if (r->child[RIGHT] == a)
+ r->child[RIGHT] = d;
+ }
+ if (racine == a)
+ racine = d;
+
+ int old_bal = d->balance;
+ d->balance = 0;
+ if (old_bal == 0)
+ {
+ a->balance = 0;
+ b->balance = 0;
+ }
+ else if (old_bal > 0)
+ {
+ a->balance = -1;
+ b->balance = 0;
+ }
+ else if (old_bal < 0)
+ {
+ a->balance = 0;
+ b->balance = 1;
+ }
+ return avl_no_err;
+ }
+ }
+ else if (balance < 0)
+ {
+ if (from == child[LEFT])
+ {
+ balance = 0;
+ return avl_no_err;
+ }
+ if (child[RIGHT] == nullptr)
+ {
+// cout << "mierda\n";
+ return avl_bal_err;
+ }
+ AVLTree *a = this;
+ AVLTree *b = child[RIGHT];
+ AVLTree *e = child[LEFT];
+ AVLTree *c = child[RIGHT]->child[RIGHT];
+ AVLTree *d = child[RIGHT]->child[LEFT];
+ AVLTree *r = parent;
+ if (child[RIGHT]->balance < 0)
+ {
+
+ a->parent = b;
+ b->child[LEFT] = a;
+ a->child[LEFT] = e;
+ if (e)
+ e->parent = a;
+ a->child[RIGHT] = d;
+ if (d)
+ d->parent = a;
+ b->child[RIGHT] = c;
+ if (c)
+ c->parent = b;
+ b->parent = r;
+ if (r)
+ {
+ if (r->child[LEFT] == a)
+ r->child[LEFT] = b;
+ if (r->child[RIGHT] == a)
+ r->child[RIGHT] = b;
+ }
+ if (racine == a)
+ racine = b;
+ a->balance = 0;
+ b->balance = 0;
+ return avl_no_err;
+ }
+ else
+ {
+ if (child[RIGHT]->child[LEFT] == nullptr)
+ {
+// cout << "mierda\n";
+ return avl_bal_err;
+ }
+ AVLTree *f = child[RIGHT]->child[LEFT]->child[RIGHT];
+ AVLTree *g = child[RIGHT]->child[LEFT]->child[LEFT];
+
+ a->parent = d;
+ d->child[LEFT] = a;
+ b->parent = d;
+ d->child[RIGHT] = b;
+ a->child[RIGHT] = g;
+ if (g)
+ g->parent = a;
+ a->child[LEFT] = e;
+ if (e)
+ e->parent = a;
+ b->child[RIGHT] = c;
+ if (c)
+ c->parent = b;
+ b->child[LEFT] = f;
+ if (f)
+ f->parent = b;
+
+ d->parent = r;
+ if (r)
+ {
+ if (r->child[LEFT] == a)
+ r->child[LEFT] = d;
+ if (r->child[RIGHT] == a)
+ r->child[RIGHT] = d;
+ }
+ if (racine == a)
+ racine = d;
+ int old_bal = d->balance;
+ d->balance = 0;
+ if (old_bal == 0)
+ {
+ a->balance = 0;
+ b->balance = 0;
+ }
+ else if (old_bal > 0)
+ {
+ a->balance = 0;
+ b->balance = -1;
+ }
+ else if (old_bal < 0)
+ {
+ a->balance = 1;
+ b->balance = 0;
+ }
+ return avl_no_err;
+ }
+ }
+ }
+ return avl_no_err;
+}
+
+int
+AVLTree::RestoreBalances (int diff, AVLTree * &racine)
+{
+ if (balance > 0)
+ {
+ if (diff < 0)
+ {
+ balance = 0;
+ if (parent)
+ {
+ if (this == parent->child[RIGHT])
+ return parent->RestoreBalances (1, racine);
+ if (this == parent->child[LEFT])
+ return parent->RestoreBalances (-1, racine);
+ }
+ return avl_no_err;
+ }
+ else if (diff == 0)
+ {
+ }
+ else if (diff > 0)
+ {
+ if (child[LEFT] == nullptr)
+ {
+// cout << "un probleme\n";
+ return avl_bal_err;
+ }
+ AVLTree *r = parent;
+ AVLTree *a = this;
+ AVLTree *b = child[RIGHT];
+ AVLTree *e = child[LEFT];
+ AVLTree *f = e->child[RIGHT];
+ AVLTree *g = e->child[LEFT];
+ if (e->balance > 0)
+ {
+ e->child[RIGHT] = a;
+ e->child[LEFT] = g;
+ a->child[RIGHT] = b;
+ a->child[LEFT] = f;
+ if (a)
+ a->parent = e;
+ if (g)
+ g->parent = e;
+ if (b)
+ b->parent = a;
+ if (f)
+ f->parent = a;
+ e->parent = r;
+ if (r)
+ {
+ if (r->child[LEFT] == a)
+ r->child[LEFT] = e;
+ if (r->child[RIGHT] == a)
+ r->child[RIGHT] = e;
+ }
+ if (racine == this)
+ racine = e;
+ e->balance = 0;
+ a->balance = 0;
+ if (r)
+ {
+ if (e == r->child[RIGHT])
+ return r->RestoreBalances (1, racine);
+ if (e == r->child[LEFT])
+ return r->RestoreBalances (-1, racine);
+ }
+ return avl_no_err;
+ }
+ else if (e->balance == 0)
+ {
+ e->child[RIGHT] = a;
+ e->child[LEFT] = g;
+ a->child[RIGHT] = b;
+ a->child[LEFT] = f;
+ if (a)
+ a->parent = e;
+ if (g)
+ g->parent = e;
+ if (b)
+ b->parent = a;
+ if (f)
+ f->parent = a;
+ e->parent = r;
+ if (r)
+ {
+ if (r->child[LEFT] == a)
+ r->child[LEFT] = e;
+ if (r->child[RIGHT] == a)
+ r->child[RIGHT] = e;
+ }
+ if (racine == this)
+ racine = e;
+ e->balance = -1;
+ a->balance = 1;
+ return avl_no_err;
+ }
+ else if (e->balance < 0)
+ {
+ if (child[LEFT]->child[RIGHT] == nullptr)
+ {
+// cout << "un probleme\n";
+ return avl_bal_err;
+ }
+ AVLTree *i = child[LEFT]->child[RIGHT]->child[RIGHT];
+ AVLTree *j = child[LEFT]->child[RIGHT]->child[LEFT];
+
+ f->child[RIGHT] = a;
+ f->child[LEFT] = e;
+ a->child[RIGHT] = b;
+ a->child[LEFT] = i;
+ e->child[RIGHT] = j;
+ e->child[LEFT] = g;
+ if (b)
+ b->parent = a;
+ if (i)
+ i->parent = a;
+ if (g)
+ g->parent = e;
+ if (j)
+ j->parent = e;
+ if (a)
+ a->parent = f;
+ if (e)
+ e->parent = f;
+ f->parent = r;
+ if (r)
+ {
+ if (r->child[LEFT] == a)
+ r->child[LEFT] = f;
+ if (r->child[RIGHT] == a)
+ r->child[RIGHT] = f;
+ }
+ if (racine == this)
+ racine = f;
+ int oBal = f->balance;
+ f->balance = 0;
+ if (oBal > 0)
+ {
+ a->balance = -1;
+ e->balance = 0;
+ }
+ else if (oBal == 0)
+ {
+ a->balance = 0;
+ e->balance = 0;
+ }
+ else if (oBal < 0)
+ {
+ a->balance = 0;
+ e->balance = 1;
+ }
+ if (r)
+ {
+ if (f == r->child[RIGHT])
+ return r->RestoreBalances (1, racine);
+ if (f == r->child[LEFT])
+ return r->RestoreBalances (-1, racine);
+ }
+ return avl_no_err;
+ }
+ }
+ }
+ else if (balance == 0)
+ {
+ if (diff < 0)
+ {
+ balance = -1;
+ }
+ else if (diff == 0)
+ {
+ }
+ else if (diff > 0)
+ {
+ balance = 1;
+ }
+ return avl_no_err;
+ }
+ else if (balance < 0)
+ {
+ if (diff < 0)
+ {
+ if (child[RIGHT] == nullptr)
+ {
+// cout << "un probleme\n";
+ return avl_bal_err;
+ }
+ AVLTree *r = parent;
+ AVLTree *a = this;
+ AVLTree *b = child[LEFT];
+ AVLTree *e = child[RIGHT];
+ AVLTree *f = e->child[LEFT];
+ AVLTree *g = e->child[RIGHT];
+ if (e->balance < 0)
+ {
+ e->child[LEFT] = a;
+ e->child[RIGHT] = g;
+ a->child[LEFT] = b;
+ a->child[RIGHT] = f;
+ if (a)
+ a->parent = e;
+ if (g)
+ g->parent = e;
+ if (b)
+ b->parent = a;
+ if (f)
+ f->parent = a;
+ e->parent = r;
+ if (r)
+ {
+ if (r->child[LEFT] == a)
+ r->child[LEFT] = e;
+ if (r->child[RIGHT] == a)
+ r->child[RIGHT] = e;
+ }
+ if (racine == this)
+ racine = e;
+ e->balance = 0;
+ a->balance = 0;
+ if (r)
+ {
+ if (e == r->child[RIGHT])
+ return r->RestoreBalances (1, racine);
+ if (e == r->child[LEFT])
+ return r->RestoreBalances (-1, racine);
+ }
+ return avl_no_err;
+ }
+ else if (e->balance == 0)
+ {
+ e->child[LEFT] = a;
+ e->child[RIGHT] = g;
+ a->child[LEFT] = b;
+ a->child[RIGHT] = f;
+ if (a)
+ a->parent = e;
+ if (g)
+ g->parent = e;
+ if (b)
+ b->parent = a;
+ if (f)
+ f->parent = a;
+ e->parent = r;
+ if (r)
+ {
+ if (r->child[LEFT] == a)
+ r->child[LEFT] = e;
+ if (r->child[RIGHT] == a)
+ r->child[RIGHT] = e;
+ }
+ if (racine == this)
+ racine = e;
+ e->balance = 1;
+ a->balance = -1;
+ return avl_no_err;
+ }
+ else if (e->balance > 0)
+ {
+ if (child[RIGHT]->child[LEFT] == nullptr)
+ {
+// cout << "un probleme\n";
+ return avl_bal_err;
+ }
+ AVLTree *i = child[RIGHT]->child[LEFT]->child[LEFT];
+ AVLTree *j = child[RIGHT]->child[LEFT]->child[RIGHT];
+
+ f->child[LEFT] = a;
+ f->child[RIGHT] = e;
+ a->child[LEFT] = b;
+ a->child[RIGHT] = i;
+ e->child[LEFT] = j;
+ e->child[RIGHT] = g;
+ if (b)
+ b->parent = a;
+ if (i)
+ i->parent = a;
+ if (g)
+ g->parent = e;
+ if (j)
+ j->parent = e;
+ if (a)
+ a->parent = f;
+ if (e)
+ e->parent = f;
+ f->parent = r;
+ if (r)
+ {
+ if (r->child[LEFT] == a)
+ r->child[LEFT] = f;
+ if (r->child[RIGHT] == a)
+ r->child[RIGHT] = f;
+ }
+ if (racine == this)
+ racine = f;
+ int oBal = f->balance;
+ f->balance = 0;
+ if (oBal > 0)
+ {
+ a->balance = 0;
+ e->balance = -1;
+ }
+ else if (oBal == 0)
+ {
+ a->balance = 0;
+ e->balance = 0;
+ }
+ else if (oBal < 0)
+ {
+ a->balance = 1;
+ e->balance = 0;
+ }
+ if (r)
+ {
+ if (f == r->child[RIGHT])
+ return r->RestoreBalances (1, racine);
+ if (f == r->child[LEFT])
+ return r->RestoreBalances (-1, racine);
+ }
+ return avl_no_err;
+ }
+ }
+ else if (diff == 0)
+ {
+ }
+ else if (diff > 0)
+ {
+ balance = 0;
+ if (parent)
+ {
+ if (this == parent->child[RIGHT])
+ return parent->RestoreBalances (1, racine);
+ if (this == parent->child[LEFT])
+ return parent->RestoreBalances (-1, racine);
+ }
+ return avl_no_err;
+ }
+ }
+ return avl_no_err;
+}
+
+/*
+ * removal
+ */
+int
+AVLTree::Remove (AVLTree * &racine, bool rebalance)
+{
+ AVLTree *startNode = nullptr;
+ int remDiff = 0;
+ int res = Remove (racine, startNode, remDiff);
+ if (res == avl_no_err && rebalance && startNode)
+ res = startNode->RestoreBalances (remDiff, racine);
+ return res;
+}
+
+int
+AVLTree::Remove (AVLTree * &racine, AVLTree * &startNode, int &diff)
+{
+ if (elem[LEFT])
+ elem[LEFT]->elem[RIGHT] = elem[RIGHT];
+ if (elem[RIGHT])
+ elem[RIGHT]->elem[LEFT] = elem[LEFT];
+ elem[LEFT] = elem[RIGHT] = nullptr;
+
+ if (child[LEFT] && child[RIGHT])
+ {
+ AVLTree *newMe = child[LEFT]->leafFromParent(this, RIGHT);
+ if (newMe == nullptr || newMe->child[RIGHT])
+ {
+// cout << "pas normal\n";
+ return avl_rm_err;
+ }
+ if (newMe == child[LEFT])
+ {
+ startNode = newMe;
+ diff = -1;
+ newMe->child[RIGHT] = child[RIGHT];
+ child[RIGHT]->parent = newMe;
+ newMe->parent = parent;
+ if (parent)
+ {
+ if (parent->child[LEFT] == this)
+ parent->child[LEFT] = newMe;
+ if (parent->child[RIGHT] == this)
+ parent->child[RIGHT] = newMe;
+ }
+ }
+ else
+ {
+ AVLTree *oParent = newMe->parent;
+ startNode = oParent;
+ diff = 1;
+
+ oParent->child[RIGHT] = newMe->child[LEFT];
+ if (newMe->child[LEFT])
+ newMe->child[LEFT]->parent = oParent;
+
+ newMe->parent = parent;
+ newMe->child[LEFT] = child[LEFT];
+ newMe->child[RIGHT] = child[RIGHT];
+ if (parent)
+ {
+ if (parent->child[LEFT] == this)
+ parent->child[LEFT] = newMe;
+ if (parent->child[RIGHT] == this)
+ parent->child[RIGHT] = newMe;
+ }
+ if (child[LEFT])
+ child[LEFT]->parent = newMe;
+ if (child[RIGHT])
+ child[RIGHT]->parent = newMe;
+ }
+ newMe->balance = balance;
+ if (racine == this)
+ racine = newMe;
+ }
+ else if (child[LEFT])
+ {
+ startNode = parent;
+ diff = 0;
+ if (parent)
+ {
+ if (this == parent->child[LEFT])
+ diff = -1;
+ if (this == parent->child[RIGHT])
+ diff = 1;
+ }
+ if (parent)
+ {
+ if (parent->child[LEFT] == this)
+ parent->child[LEFT] = child[LEFT];
+ if (parent->child[RIGHT] == this)
+ parent->child[RIGHT] = child[LEFT];
+ }
+ if (child[LEFT]->parent == this)
+ child[LEFT]->parent = parent;
+ if (racine == this)
+ racine = child[LEFT];
+ }
+ else if (child[RIGHT])
+ {
+ startNode = parent;
+ diff = 0;
+ if (parent)
+ {
+ if (this == parent->child[LEFT])
+ diff = -1;
+ if (this == parent->child[RIGHT])
+ diff = 1;
+ }
+ if (parent)
+ {
+ if (parent->child[LEFT] == this)
+ parent->child[LEFT] = child[RIGHT];
+ if (parent->child[RIGHT] == this)
+ parent->child[RIGHT] = child[RIGHT];
+ }
+ if (child[RIGHT]->parent == this)
+ child[RIGHT]->parent = parent;
+ if (racine == this)
+ racine = child[RIGHT];
+ }
+ else
+ {
+ startNode = parent;
+ diff = 0;
+ if (parent)
+ {
+ if (this == parent->child[LEFT])
+ diff = -1;
+ if (this == parent->child[RIGHT])
+ diff = 1;
+ }
+ if (parent)
+ {
+ if (parent->child[LEFT] == this)
+ parent->child[LEFT] = nullptr;
+ if (parent->child[RIGHT] == this)
+ parent->child[RIGHT] = nullptr;
+ }
+ if (racine == this)
+ racine = nullptr;
+ }
+ parent = child[RIGHT] = child[LEFT] = nullptr;
+ balance = 0;
+ return avl_no_err;
+}
+
+/*
+ * insertion
+ */
+int
+AVLTree::Insert (AVLTree * &racine, int insertType, AVLTree * insertL,
+ AVLTree * insertR, bool rebalance)
+{
+ int res = Insert (racine, insertType, insertL, insertR);
+ if (res == avl_no_err && rebalance)
+ res = RestoreBalances ((AVLTree *) nullptr, racine);
+ return res;
+}
+
+int
+AVLTree::Insert (AVLTree * &racine, int insertType, AVLTree * insertL,
+ AVLTree * insertR)
+{
+ if (racine == nullptr)
+ {
+ racine = this;
+ return avl_no_err;
+ }
+ else
+ {
+ if (insertType == not_found)
+ {
+// cout << "pb avec l'arbre de raster\n";
+ return avl_ins_err;
+ }
+ else if (insertType == found_on_left)
+ {
+ if (insertR == nullptr || insertR->child[LEFT])
+ {
+// cout << "ngou?\n";
+ return avl_ins_err;
+ }
+ insertR->child[LEFT] = this;
+ parent = insertR;
+ insertOn(LEFT, insertR);
+ }
+ else if (insertType == found_on_right)
+ {
+ if (insertL == nullptr || insertL->child[RIGHT])
+ {
+// cout << "ngou?\n";
+ return avl_ins_err;
+ }
+ insertL->child[RIGHT] = this;
+ parent = insertL;
+ insertOn(RIGHT, insertL);
+ }
+ else if (insertType == found_between)
+ {
+ if (insertR == nullptr || insertL == nullptr
+ || (insertR->child[LEFT] != nullptr && insertL->child[RIGHT] != nullptr))
+ {
+// cout << "ngou?\n";
+ return avl_ins_err;
+ }
+ if (insertR->child[LEFT] == nullptr)
+ {
+ insertR->child[LEFT] = this;
+ parent = insertR;
+ }
+ else if (insertL->child[RIGHT] == nullptr)
+ {
+ insertL->child[RIGHT] = this;
+ parent = insertL;
+ }
+ insertBetween (insertL, insertR);
+ }
+ else if (insertType == found_exact)
+ {
+ if (insertL == nullptr)
+ {
+// cout << "ngou?\n";
+ return avl_ins_err;
+ }
+ // et on insere
+
+ if (insertL->child[RIGHT])
+ {
+ insertL = insertL->child[RIGHT]->leafFromParent(insertL, LEFT);
+ if (insertL->child[LEFT])
+ {
+// cout << "ngou?\n";
+ return avl_ins_err;
+ }
+ insertL->child[LEFT] = this;
+ this->parent = insertL;
+ insertBetween (insertL->elem[LEFT], insertL);
+ }
+ else
+ {
+ insertL->child[RIGHT] = this;
+ parent = insertL;
+ insertBetween (insertL, insertL->elem[RIGHT]);
+ }
+ }
+ else
+ {
+ // cout << "code incorrect\n";
+ return avl_ins_err;
+ }
+ }
+ return avl_no_err;
+}
+
+void
+AVLTree::Relocate (AVLTree * to)
+{
+ if (elem[LEFT])
+ elem[LEFT]->elem[RIGHT] = to;
+ if (elem[RIGHT])
+ elem[RIGHT]->elem[LEFT] = to;
+ to->elem[LEFT] = elem[LEFT];
+ to->elem[RIGHT] = elem[RIGHT];
+
+ if (parent)
+ {
+ if (parent->child[LEFT] == this)
+ parent->child[LEFT] = to;
+ if (parent->child[RIGHT] == this)
+ parent->child[RIGHT] = to;
+ }
+ if (child[RIGHT])
+ {
+ child[RIGHT]->parent = to;
+ }
+ if (child[LEFT])
+ {
+ child[LEFT]->parent = to;
+ }
+ to->parent = parent;
+ to->child[RIGHT] = child[RIGHT];
+ to->child[LEFT] = child[LEFT];
+}
+
+
+void AVLTree::insertOn(Side s, AVLTree *of)
+{
+ elem[1 - s] = of;
+ if (of)
+ of->elem[s] = this;
+}
+
+void AVLTree::insertBetween(AVLTree *l, AVLTree *r)
+{
+ if (l)
+ l->elem[RIGHT] = this;
+ if (r)
+ r->elem[LEFT] = this;
+ elem[LEFT] = l;
+ elem[RIGHT] = r;
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/livarot/AVL.h b/src/livarot/AVL.h
new file mode 100644
index 0000000..5e0856c
--- /dev/null
+++ b/src/livarot/AVL.h
@@ -0,0 +1,104 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * TODO: insert short description here
+ *//*
+ * Authors: see git history
+ *
+ * Copyright (C) 2014 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+/*
+ * AVL.h
+ * nlivarot
+ *
+ * Created by fred on Mon Jun 16 2003.
+ *
+ */
+
+#ifndef my_avl
+#define my_avl
+
+#include <cstdlib>
+#include "LivarotDefs.h"
+
+/*
+ * base class providing AVL tree functionnality, that is binary balanced tree
+ * there is no Find() function because the class only deal with topological info
+ * subclasses of this class have to implement a Find(), and most certainly to
+ * override the Insert() function
+ */
+
+class AVLTree
+{
+public:
+
+ AVLTree *elem[2];
+
+ // left most node (ie, with smallest key) in the subtree of this node
+ AVLTree *Leftmost();
+
+protected:
+
+ AVLTree *child[2];
+
+ AVLTree();
+ virtual ~AVLTree();
+
+ // constructor/destructor meant to be called for an array of AVLTree created by malloc
+ void MakeNew();
+ void MakeDelete();
+
+ // insertion of the present node in the tree
+ // insertType is the insertion type (defined in LivarotDefs.h: not_found, found_exact, found_on_left, etc)
+ // insertL is the node in the tree that is immediatly before the current one, NULL is the present node goes to the
+ // leftmost position. if insertType == found_exact, insertL should be the node with ak key
+ // equal to that of the present node
+ int Insert(AVLTree * &racine, int insertType, AVLTree *insertL,
+ AVLTree * insertR, bool rebalance);
+
+ // called when this node is relocated to a new position in memory, to update pointers to him
+ void Relocate(AVLTree *to);
+
+ // removal of the present element racine is the tree's root; it's a reference because if the
+ // node is the root, removal of the node will change the root
+ // rebalance==true if rebalancing is needed
+ int Remove(AVLTree * &racine, bool rebalance = true);
+
+private:
+
+ AVLTree *parent;
+
+ int balance;
+
+ // insertion gruntwork.
+ int Insert(AVLTree * &racine, int insertType, AVLTree *insertL, AVLTree *insertR);
+
+ // rebalancing functions. both are recursive, but the depth of the trees we'll use should not be a problem
+ // this one is for rebalancing after insertions
+ int RestoreBalances(AVLTree *from, AVLTree * &racine);
+ // this one is for removals
+ int RestoreBalances(int diff, AVLTree * &racine);
+
+ // startNode is the node where the rebalancing starts; rebalancing then moves up the tree to the root
+ // diff is the change in "subtree height", as needed for the rebalancing
+ // racine is the reference to the root, since rebalancing can change it too
+ int Remove(AVLTree * &racine, AVLTree * &startNode, int &diff);
+
+ void insertOn(Side s, AVLTree *of);
+ void insertBetween(AVLTree *l, AVLTree *r);
+ AVLTree *leaf(AVLTree *from, Side s);
+ AVLTree *leafFromParent(AVLTree *from, Side s);
+};
+
+#endif
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/livarot/AlphaLigne.cpp b/src/livarot/AlphaLigne.cpp
new file mode 100644
index 0000000..7ae72c1
--- /dev/null
+++ b/src/livarot/AlphaLigne.cpp
@@ -0,0 +1,308 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * TODO: insert short description here
+ *//*
+ * Authors:
+ * see git history
+ * Fred
+ *
+ * Copyright (C) 2018 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "AlphaLigne.h"
+
+#include <cmath>
+#include <cstdio>
+#include <cstdlib>
+#include <glib.h>
+
+AlphaLigne::AlphaLigne(int iMin,int iMax)
+{
+ min=iMin;
+ max=iMax;
+ if ( max < min+1 ) max=min+1;
+ steps=nullptr;
+ nbStep=maxStep=0;
+ before.x=min-1;
+ before.delta=0;
+ after.x=max+1;
+ after.delta=0;
+}
+AlphaLigne::~AlphaLigne()
+{
+ g_free(steps);
+ steps=nullptr;
+ nbStep=maxStep=0;
+}
+void AlphaLigne::Affiche()
+{
+ printf("%i steps\n",nbStep);
+ for (int i=0;i<nbStep;i++) {
+ printf("(%i %f) ",steps[i].x,steps[i].delta); // localization ok
+ }
+ printf("\n");
+}
+
+
+void AlphaLigne::Reset()
+{
+ // reset to empty line
+ // doesn't deallocate the steps array, to minimize memory operations
+ curMin=max;
+ curMax=min;
+ nbStep=0;
+ before.x=min-1;
+ before.delta=0;
+ after.x=max+1;
+ after.delta=0;
+}
+int AlphaLigne::AddBord(float spos,float sval,float epos,float eval,float tPente)
+{
+// printf("%f %f -> %f %f / %f\n",spos,sval,epos,eval,tPente);
+ if ( sval == eval ) return 0;
+ // compute the footprint of [spos,epos] on the line of pixels
+ float curStF=floor(spos);
+ float curEnF=floor(epos);
+ int curSt=(int)curStF;
+ int curEn=(int)curEnF;
+
+ // update curMin and curMax
+ if ( curSt > max ) {
+ // we're on the right of the visible portion of the line: bail out!
+ if ( eval < sval ) curMax=max;
+ return 0;
+ }
+ if ( curSt < curMin ) curMin=curSt;
+ if ( ceil(epos) > curMax ) curMax=(int)ceil(epos);
+
+ // clamp the changed portion to [min,max], no need for bigger
+ if ( curMax > max ) curMax=max;
+ if ( curMin < min ) curMin=min;
+
+ // total amount of change in pixel coverage from before the right to after the run
+ float needed=eval-sval;
+ float needC=/*(int)ldexpf(*/needed/*,24)*/;
+
+ if ( curEn < min ) {
+ // the added portion is entirely on the left, so we only have to change the initial coverage for the line
+ before.delta+=needC;
+ return 0;
+ }
+
+ // add the steps
+ // the pixels from [curSt..curEn] (included) intersect with [spos;epos]
+ // since we're dealing with delta in the coverage, there is also a curEn+1 delta, since the curEn pixel intersect
+ // with [spos;epos] and thus has some delta with respect to its next pixel
+ // lots of different cases... ugly
+ if ( curSt == curEn ) {
+ if ( curSt+1 < min ) {
+ before.delta+=needC;
+ } else {
+ if ( nbStep+2 >= maxStep ) {
+ maxStep=2*nbStep+2;
+ steps=(alpha_step*)g_realloc(steps,maxStep*sizeof(alpha_step));
+ }
+ float stC=/*(int)ldexpf(*/(eval-sval)*(0.5*(epos-spos)+curStF+1-epos)/*,24)*/;
+ steps[nbStep].x=curSt;
+ steps[nbStep].delta=stC;
+ nbStep++;
+ steps[nbStep].x=curSt+1;
+ steps[nbStep].delta=needC-stC; // au final, on a toujours le bon delta, meme avec une arete completement verticale
+ nbStep++;
+ }
+ } else if ( curEn == curSt+1 ) {
+ if ( curSt+2 < min ) {
+ before.delta+=needC;
+ } else {
+ if ( nbStep+3 >= maxStep ) {
+ maxStep=2*nbStep+3;
+ steps=(alpha_step*)g_realloc(steps,maxStep*sizeof(alpha_step));
+ }
+ float stC=/*(int)ldexpf(*/0.5*tPente*(curEnF-spos)*(curEnF-spos)/*,24)*/;
+ float enC=/*(int)ldexpf(*/tPente-0.5*tPente*((spos-curStF)*(spos-curStF)+(curEnF+1.0-epos)*(curEnF+1.0-epos))/*,24)*/;
+ steps[nbStep].x=curSt;
+ steps[nbStep].delta=stC;
+ nbStep++;
+ steps[nbStep].x=curEn;
+ steps[nbStep].delta=enC;
+ nbStep++;
+ steps[nbStep].x=curEn+1;
+ steps[nbStep].delta=needC-stC-enC;
+ nbStep++;
+ }
+ } else {
+ float stC=/*(int)ldexpf(*/0.5*tPente*(curStF+1-spos)*(curStF+1-spos)/*,24)*/;
+ float stFC=/*(int)ldexpf(*/tPente-0.5*tPente*(spos-curStF)*(spos-curStF)/*,24)*/;
+ float enC=/*(int)ldexpf(*/tPente-0.5*tPente*(curEnF+1.0-epos)*(curEnF+1.0-epos)/*,24)*/;
+ float miC=/*(int)ldexpf(*/tPente/*,24)*/;
+ if ( curSt < min ) {
+ if ( curEn > max ) {
+ if ( nbStep+(max-min) >= maxStep ) {
+ maxStep=2*nbStep+(max-min);
+ steps=(alpha_step*)g_realloc(steps,maxStep*sizeof(alpha_step));
+ }
+ float bfd=min-curSt-1;
+ bfd*=miC;
+ before.delta+=stC+bfd;
+ for (int i=min;i<max;i++) {
+ steps[nbStep].x=i;
+ steps[nbStep].delta=miC;
+ nbStep++;
+ }
+ } else {
+ if ( nbStep+(curEn-min)+2 >= maxStep ) {
+ maxStep=2*nbStep+(curEn-min)+2;
+ steps=(alpha_step*)g_realloc(steps,maxStep*sizeof(alpha_step));
+ }
+ float bfd=min-curSt-1;
+ bfd*=miC;
+ before.delta+=stC+bfd;
+ for (int i=min;i<curEn;i++) {
+ steps[nbStep].x=i;
+ steps[nbStep].delta=miC;
+ nbStep++;
+ }
+ steps[nbStep].x=curEn;
+ steps[nbStep].delta=enC;
+ nbStep++;
+ steps[nbStep].x=curEn+1;
+ steps[nbStep].delta=needC-stC-stFC-enC-(curEn-curSt-2)*miC;
+ nbStep++;
+ }
+ } else {
+ if ( curEn > max ) {
+ if ( nbStep+3+(max-curSt) >= maxStep ) {
+ maxStep=2*nbStep+3+(curEn-curSt);
+ steps=(alpha_step*)g_realloc(steps,maxStep*sizeof(alpha_step));
+ }
+ steps[nbStep].x=curSt;
+ steps[nbStep].delta=stC;
+ nbStep++;
+ steps[nbStep].x=curSt+1;
+ steps[nbStep].delta=stFC;
+ nbStep++;
+ for (int i=curSt+2;i<max;i++) {
+ steps[nbStep].x=i;
+ steps[nbStep].delta=miC;
+ nbStep++;
+ }
+ } else {
+ if ( nbStep+3+(curEn-curSt) >= maxStep ) {
+ maxStep=2*nbStep+3+(curEn-curSt);
+ steps=(alpha_step*)g_realloc(steps,maxStep*sizeof(alpha_step));
+ }
+ steps[nbStep].x=curSt;
+ steps[nbStep].delta=stC;
+ nbStep++;
+ steps[nbStep].x=curSt+1;
+ steps[nbStep].delta=stFC;
+ nbStep++;
+ for (int i=curSt+2;i<curEn;i++) {
+ steps[nbStep].x=i;
+ steps[nbStep].delta=miC;
+ nbStep++;
+ }
+ steps[nbStep].x=curEn;
+ steps[nbStep].delta=enC;
+ nbStep++;
+ steps[nbStep].x=curEn+1;
+ steps[nbStep].delta=needC-stC-stFC-enC-(curEn-curSt-2)*miC;
+ nbStep++;
+ }
+ }
+ }
+
+ return 0;
+}
+int AlphaLigne::AddBord(float spos,float sval,float epos,float eval)
+{
+ // pas de pente dans ce cas; on ajoute le delta au premier pixel
+ float tPente=(eval-sval);
+
+ float curStF=floor(spos);
+ float curEnF=floor(epos);
+ int curSt=(int)curStF;
+ int curEn=(int)curEnF;
+
+ if ( curSt > max ) {
+ if ( eval < sval ) curMax=max;
+ return 0; // en dehors des limites (attention a ne pas faire ca avec curEn)
+ }
+ if ( curEn < min ) {
+ before.delta+=eval-sval;
+ return 0; // en dehors des limites (attention a ne pas faire ca avec curEn)
+ }
+
+ if ( curSt < curMin ) curMin=curSt;
+// int curEn=(int)curEnF;
+ if ( ceil(epos) > curMax-1 ) curMax=1+(int)ceil(epos);
+ if ( curSt < min ) {
+ before.delta+=eval-sval;
+ } else {
+ AddRun(curSt,/*(int)ldexpf(*/(((float)(curSt+1))-spos)*tPente/*,24)*/);
+ AddRun(curSt+1,/*(int)ldexpf(*/(spos-((float)(curSt)))*tPente/*,24)*/);
+ }
+ return 0;
+}
+
+void AlphaLigne::Flatten()
+{
+ // just sort
+ if ( nbStep > 0 ) qsort(steps,nbStep,sizeof(alpha_step),CmpStep);
+}
+void AlphaLigne::AddRun(int st,float pente)
+{
+ if ( nbStep >= maxStep ) {
+ maxStep=2*nbStep+1;
+ steps=(alpha_step*)g_realloc(steps,maxStep*sizeof(alpha_step));
+ }
+ int nStep=nbStep++;
+ steps[nStep].x=st;
+ steps[nStep].delta=pente;
+}
+
+void AlphaLigne::Raster(raster_info &dest,void* color,RasterInRunFunc worker)
+{
+ // start by checking if there are actually pixels in need of rasterization
+ if ( curMax <= curMin ) return;
+ if ( dest.endPix <= curMin || dest.startPix >= curMax ) return;
+
+ int nMin=curMin,nMax=curMax;
+ float alpSum=before.delta; // alpSum will be the pixel coverage value, so we start at before.delta
+ int curStep=0;
+
+ // first add all the deltas up to the first pixel in need of rasterization
+ while ( curStep < nbStep && steps[curStep].x < nMin ) {
+ alpSum+=steps[curStep].delta;
+ curStep++;
+ }
+ // just in case, if the line bounds are greater than the buffer bounds.
+ if ( nMin < dest.startPix ) {
+ for (;( curStep < nbStep && steps[curStep].x < dest.startPix) ;curStep++) alpSum+=steps[curStep].delta;
+ nMin=dest.startPix;
+ }
+ if ( nMax > dest.endPix ) nMax=dest.endPix;
+
+ // raster!
+ int curPos=dest.startPix;
+ for (;curStep<nbStep;curStep++) {
+ if ( alpSum > 0 && steps[curStep].x > curPos ) {
+ // we're going to change the pixel position curPos, and alpSum is > 0: rasterization needed from
+ // the last position (curPos) up to the pixel we're moving to (steps[curStep].x)
+ int nst=curPos,nen=steps[curStep].x;
+//Buffer::RasterRun(dest,color,nst,alpSum,nen,alpSum);
+ (worker)(dest,color,nst,alpSum,nen,alpSum);
+ }
+ // add coverage deltas
+ alpSum+=steps[curStep].delta;
+ curPos=steps[curStep].x;
+ if ( curPos >= nMax ) break;
+ }
+ // if we ended the line with alpSum > 0, we need to raster from curPos to the right edge
+ if ( alpSum > 0 && curPos < nMax ) {
+ int nst=curPos,nen=max;
+ (worker)(dest,color,nst,alpSum,nen,alpSum);
+//Buffer::RasterRun(dest,color,nst,alpSum,nen,alpSum);
+ }
+}
diff --git a/src/livarot/AlphaLigne.h b/src/livarot/AlphaLigne.h
new file mode 100644
index 0000000..a192e1c
--- /dev/null
+++ b/src/livarot/AlphaLigne.h
@@ -0,0 +1,87 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * TODO: insert short description here
+ *//*
+ * Authors:
+ * see git history
+ * Fred
+ *
+ * Copyright (C) 2018 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef my_alpha_ligne
+#define my_alpha_ligne
+
+#include "LivarotDefs.h"
+
+/*
+ * pixel coverage of a line, libart style: each pixel coverage is obtained from the coverage of the previous one by
+ * adding a delta given by a step. the goal is to have only a limited number of positions where the delta != 0, so that
+ * you only have to store a limited number of steps.
+ */
+
+// a step
+struct alpha_step {
+ int x; // position
+ float delta; // increase or decrease in pixel coverage with respect to the coverage of the previous pixel
+};
+
+
+class AlphaLigne {
+public:
+ // bounds of the line
+ // necessary since the visible portion of the canvas is bounded, and you need to compute
+ // the value of the pixel "just before the visible portion of the line"
+ int min,max;
+ int length;
+
+ // before is the step containing the delta relative to a pixel infinitely far on the left of the line
+ // thus the initial pixel coverage is before.delta
+ alpha_step before,after;
+ // array of steps
+ int nbStep,maxStep;
+ alpha_step* steps;
+
+ // bounds of the portion of the line that has received some coverage
+ int curMin,curMax;
+
+ // iMin and iMax are the bounds of the visible portion of the line
+ AlphaLigne(int iMin,int iMax);
+ virtual ~AlphaLigne();
+
+ // empties the line
+ void Reset();
+
+ // add some coverage.
+ // pente is (eval-sval)/(epos-spos), because you can compute it once per edge, and thus spare the
+ // CPU some potentially costly divisions
+ int AddBord(float spos,float sval,float epos,float eval,float iPente);
+ // version where you don't have the pente parameter
+ int AddBord(float spos,float sval,float epos,float eval);
+
+ // sorts the steps in increasing order. needed before you raster the line
+ void Flatten();
+
+ // debug dump of the steps
+ void Affiche();
+
+ // private
+ void AddRun(int st,float pente);
+
+ // raster the line in the buffer given in "dest", with the rasterization primitive worker
+ // worker() is given the color parameter each time it is called. the type of the function is
+ // defined in LivarotDefs.h
+ void Raster(raster_info &dest,void* color,RasterInRunFunc worker);
+
+ // also private. that's the comparison function given to qsort()
+ static int CmpStep(const void * p1, const void * p2) {
+ alpha_step* d1=(alpha_step*)p1;
+ alpha_step* d2=(alpha_step*)p2;
+ return d1->x - d2->x ;
+ };
+};
+
+
+#endif
+
diff --git a/src/livarot/BitLigne.cpp b/src/livarot/BitLigne.cpp
new file mode 100644
index 0000000..2e44359
--- /dev/null
+++ b/src/livarot/BitLigne.cpp
@@ -0,0 +1,180 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * TODO: insert short description here
+ *//*
+ * Authors:
+ * see git history
+ * Fred
+ *
+ * Copyright (C) 2018 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "BitLigne.h"
+
+#include <cmath>
+#include <cstring>
+#include <cstdlib>
+#include <string>
+#include <cmath>
+#include <cstdio>
+#include <glib.h>
+
+BitLigne::BitLigne(int ist,int ien,float iScale)
+{
+ scale=iScale;
+ invScale=1/iScale;
+ st=ist;
+ en=ien;
+ if ( en <= st ) en=st+1;
+ stBit=(int)floor(((float)st)*invScale); // round to pixel boundaries in the canvas
+ enBit=(int)ceil(((float)en)*invScale);
+ int nbBit=enBit-stBit;
+ if ( nbBit&31 ) {
+ nbInt=nbBit/32+1;
+ } else {
+ nbInt=nbBit/32;
+ }
+ nbInt+=1;
+ fullB=(uint32_t*)g_malloc(nbInt*sizeof(uint32_t));
+ partB=(uint32_t*)g_malloc(nbInt*sizeof(uint32_t));
+
+ curMin=en;
+ curMax=st;
+}
+BitLigne::~BitLigne()
+{
+ g_free(fullB);
+ g_free(partB);
+}
+
+void BitLigne::Reset()
+{
+ curMin=en;
+ curMax=st+1;
+ memset(fullB,0,nbInt*sizeof(uint32_t));
+ memset(partB,0,nbInt*sizeof(uint32_t));
+}
+int BitLigne::AddBord(float spos,float epos,bool full)
+{
+ if ( spos >= epos ) return 0;
+
+ // separation of full and not entirely full bits is a bit useless
+ // the goal is to obtain a set of bits that are "on the edges" of the polygon, so that their coverage
+ // will be 1/2 on the average. in practice it's useless for anything but the even-odd fill rule
+ int ffBit,lfBit; // first and last bit of the portion of the line that is entirely covered
+ ffBit=(int)(ceil(invScale*spos));
+ lfBit=(int)(floor(invScale*epos));
+ int fpBit,lpBit; // first and last bit of the portion of the line that is not entirely but partially covered
+ fpBit=(int)(floor(invScale*spos));
+ lpBit=(int)(ceil(invScale*epos));
+
+ // update curMin and curMax to reflect the start and end pixel that need to be updated on the canvas
+ if ( floor(spos) < curMin ) curMin=(int)floor(spos);
+ if ( ceil(epos) > curMax ) curMax=(int)ceil(epos);
+
+ // clamp to the line
+ if ( ffBit < stBit ) ffBit=stBit;
+ if ( ffBit > enBit ) ffBit=enBit;
+ if ( lfBit < stBit ) lfBit=stBit;
+ if ( lfBit > enBit ) lfBit=enBit;
+ if ( fpBit < stBit ) fpBit=stBit;
+ if ( fpBit > enBit ) fpBit=enBit;
+ if ( lpBit < stBit ) lpBit=stBit;
+ if ( lpBit > enBit ) lpBit=enBit;
+
+ // offset to get actual bit position in the array
+ ffBit-=stBit;
+ lfBit-=stBit;
+ fpBit-=stBit;
+ lpBit-=stBit;
+
+ // get the end and start indices of the elements of fullB and partB that will receives coverage
+ int ffPos=ffBit>>5;
+ int lfPos=lfBit>>5;
+ int fpPos=fpBit>>5;
+ int lpPos=lpBit>>5;
+ // get bit numbers in the last and first changed elements of the fullB and partB arrays
+ int ffRem=ffBit&31;
+ int lfRem=lfBit&31;
+ int fpRem=fpBit&31;
+ int lpRem=lpBit&31;
+ // add the coverage
+ // note that the "full" bits are always a subset of the "not empty" bits, ie of the partial bits
+ // the function is a bit lame: since there is at most one bit that is partial but not full, or no full bit,
+ // it does 2 times the optimal amount of work when the coverage is full. but i'm too lazy to change that...
+ if ( fpPos == lpPos ) { // only one element of the arrays is modified
+ // compute the vector of changed bits in the element
+ uint32_t add=0xFFFFFFFF;
+ if ( lpRem < 32 ) {add>>=32-lpRem;add<<=32-lpRem; }
+ if ( lpRem <= 0 ) add=0;
+ if ( fpRem > 0) {add<<=fpRem;add>>=fpRem;}
+ // and put it in the line
+ fullB[fpPos]&=~(add); // partial is exclusive from full, so partial bits are removed from fullB
+ partB[fpPos]|=add; // and added to partB
+ if ( full ) { // if the coverage is full, add the vector of full bits
+ if ( ffBit <= lfBit ) {
+ add=0xFFFFFFFF;
+ if ( lfRem < 32 ) {add>>=32-lfRem;add<<=32-lfRem;}
+ if ( lfRem <= 0 ) add=0;
+ if ( ffRem > 0 ) {add<<=ffRem;add>>=ffRem;}
+ fullB[ffPos]|=add;
+ partB[ffPos]&=~(add);
+ }
+ }
+ } else {
+ // first and last elements are differents, so add what appropriate to each
+ uint32_t add=0xFFFFFFFF;
+ if ( fpRem > 0 ) {add<<=fpRem;add>>=fpRem;}
+ fullB[fpPos]&=~(add);
+ partB[fpPos]|=add;
+
+ add=0xFFFFFFFF;
+ if ( lpRem < 32 ) {add>>=32-lpRem;add<<=32-lpRem;}
+ if ( lpRem <= 0 ) add=0;
+ fullB[lpPos]&=~(add);
+ partB[lpPos]|=add;
+
+ // and fill what's in between with partial bits
+ if ( lpPos > fpPos+1 ) memset(fullB+(fpPos+1),0x00,(lpPos-fpPos-1)*sizeof(uint32_t));
+ if ( lpPos > fpPos+1 ) memset(partB+(fpPos+1),0xFF,(lpPos-fpPos-1)*sizeof(uint32_t));
+
+ if ( full ) { // is the coverage is full, do your magic
+ if ( ffBit <= lfBit ) {
+ if ( ffPos == lfPos ) {
+ add=0xFFFFFFFF;
+ if ( lfRem < 32 ) {add>>=32-lfRem;add<<=32-lfRem;}
+ if ( lfRem <= 0 ) add=0;
+ if ( ffRem > 0 ) {add<<=ffRem;add>>=ffRem;}
+ fullB[ffPos]|=add;
+ partB[ffPos]&=~(add);
+ } else {
+ add=0xFFFFFFFF;
+ if ( ffRem > 0 ) {add<<=ffRem;add>>=ffRem;}
+ fullB[ffPos]|=add;
+ partB[ffPos]&=~add;
+
+ add=0xFFFFFFFF;
+ if ( lfRem < 32 ) {add>>=32-lfRem;add<<=32-lfRem;}
+ if ( lfRem <= 0 ) add=0;
+ fullB[lfPos]|=add;
+ partB[lfPos]&=~add;
+
+ if ( lfPos > ffPos+1 ) memset(fullB+(ffPos+1),0xFF,(lfPos-ffPos-1)*sizeof(uint32_t));
+ if ( lfPos > ffPos+1 ) memset(partB+(ffPos+1),0x00,(lfPos-ffPos-1)*sizeof(uint32_t));
+ }
+ }
+ }
+ }
+ return 0;
+}
+
+
+void BitLigne::Affiche()
+{
+ for (int i=0;i<nbInt;i++) printf(" %.8x",fullB[i]);
+ printf("\n");
+ for (int i=0;i<nbInt;i++) printf(" %.8x",partB[i]);
+ printf("\n\n");
+}
+
diff --git a/src/livarot/BitLigne.h b/src/livarot/BitLigne.h
new file mode 100644
index 0000000..7a7fcbe
--- /dev/null
+++ b/src/livarot/BitLigne.h
@@ -0,0 +1,64 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * TODO: insert short description here
+ *//*
+ * Authors:
+ * see git history
+ * Fred
+ *
+ * Copyright (C) 2018 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef my_bit_ligne
+#define my_bit_ligne
+
+#include "LivarotDefs.h"
+
+/*
+ * a line of bits used for rasterizations of polygons
+ * the Scan() and QuickScan() functions fill the line with bits; after that you can use the Copy() function
+ * of the IntLigne class to have a set of pixel coverage runs
+ */
+
+class BitLigne {
+public:
+ // start and end pixels of the line
+ int st,en;
+ // start and end bits of the line
+ int stBit,enBit;
+ // size of the fullB and partB arrays
+ int nbInt;
+ // arrays of uint32_t used to store the bits
+ // bits of fullB mean "this pixel/bit is entirely covered"
+ // bits of partB mean "this pixel/bit is not entirely covered" (a better use would be: "this pixel is at least partially covered)
+ // so it's in fact a triage mask
+ uint32_t* fullB;
+ uint32_t* partB;
+
+ // when adding bits, these 2 values are updated to reflect which portion of the line has received coverage
+ int curMin,curMax;
+ // invScale is: canvas -> bit in the line
+ // scale is: bit -> canvas, ie the size (width) of a bit
+ float scale,invScale;
+
+ BitLigne(int ist,int ien,float iScale=0.25); // default scale is 1/4 for 4x4 supersampling
+ virtual ~BitLigne();
+
+ // reset the line to full empty
+ void Reset();
+
+ // put coverage from spos to epos (in canvas coordinates)
+ // full==true means that the bits from (fractional) position spos to epos are entirely covered
+ // full==false means the bits are not entirely covered, ie this is an edge
+ // see the Scan() and AvanceEdge() functions to see the difference
+ int AddBord(float spos,float epos,bool full);
+
+ // debug dump
+ void Affiche();
+
+};
+
+#endif
+
+
diff --git a/src/livarot/CMakeLists.txt b/src/livarot/CMakeLists.txt
new file mode 100644
index 0000000..398f7a2
--- /dev/null
+++ b/src/livarot/CMakeLists.txt
@@ -0,0 +1,44 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+set(livarot_SRC
+ AlphaLigne.cpp
+ AVL.cpp
+ BitLigne.cpp
+ float-line.cpp
+ int-line.cpp
+ PathConversion.cpp
+ Path.cpp
+ PathCutting.cpp
+ path-description.cpp
+ PathOutline.cpp
+ PathSimplify.cpp
+ PathStroke.cpp
+ Shape.cpp
+ ShapeDraw.cpp
+ ShapeMisc.cpp
+ ShapeRaster.cpp
+ ShapeSweep.cpp
+ sweep-event.cpp
+ sweep-tree.cpp
+ sweep-tree-list.cpp
+
+
+ # -------
+ # Headers
+ AVL.h
+ AlphaLigne.h
+ BitLigne.h
+ LivarotDefs.h
+ Path.h
+ Shape.h
+ float-line.h
+ int-line.h
+ path-description.h
+ sweep-event-queue.h
+ sweep-event.h
+ sweep-tree-list.h
+ sweep-tree.h
+)
+
+add_inkscape_lib(livarot_LIB "${livarot_SRC}")
+target_link_libraries(livarot_LIB PUBLIC 2Geom::2geom) \ No newline at end of file
diff --git a/src/livarot/LivarotDefs.h b/src/livarot/LivarotDefs.h
new file mode 100644
index 0000000..9940cde
--- /dev/null
+++ b/src/livarot/LivarotDefs.h
@@ -0,0 +1,95 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * TODO: insert short description here
+ *//*
+ * Authors:
+ * see git history
+ * Fred
+ *
+ * Copyright (C) 2018 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef my_defs
+#define my_defs
+
+#include <cstdint>
+
+// error codes (mostly obsolete)
+enum
+{
+ avl_no_err = 0, // 0 is the error code for "everything OK"
+ avl_bal_err = 1,
+ avl_rm_err = 2,
+ avl_ins_err = 3,
+ shape_euler_err = 4, // computations result in a non-eulerian graph, thus the function cannot do a proper polygon
+ // despite the rounding sheme, this still happen with uber-complex graphs
+ // note that coordinates are stored in double => double precision for the computation is not even
+ // enough to get exact results (need quadruple precision, i think).
+ shape_input_err = 5, // the function was given an incorrect input (not a polygon, or not eulerian)
+ shape_nothing_to_do = 6 // the function had nothing to do (zero offset, etc)
+};
+
+// return codes for the find function in the AVL tree (private)
+
+/**
+ * The SweepTree::Find function and its variant for a single point figure out where a point or an edge
+ * should be inserted in a linked list of edges. Once calculated, they return one of these values to indicate
+ * how that place looks like.
+ */
+enum
+{
+ not_found = 0, /*!< Didn't find a place. */
+ found_exact = 1, /*!< Found such an edge where edge to insert lies directly on top of another edge */
+ found_on_left = 2, /*!< Point/edge should go to the left of some edge. (There is nothing on the left of that edge) */
+ found_on_right = 3, /*!< Point/edge should go to the right of some edge. (There is nothing on the right of that edge) */
+ found_between = 4 /*!< Point/edge should go in between two particular edges. */
+};
+
+// types of cap for stroking polylines
+enum butt_typ
+{
+ butt_straight, // straight line
+ butt_square, // half square
+ butt_round, // half circle
+ butt_pointy // a little pointy hat
+};
+// types of joins for stroking paths
+enum join_typ
+{
+ join_straight, // a straight line
+ join_round, // arc of circle (in fact, one or two quadratic bezier curve chunks)
+ join_pointy // a miter join (uses the miter parameter)
+};
+typedef enum butt_typ ButtType;
+typedef enum join_typ JoinType;
+
+enum fill_typ
+{
+ fill_oddEven = 0,
+ fill_nonZero = 1,
+ fill_positive = 2,
+ fill_justDont = 3
+};
+typedef enum fill_typ FillRule;
+
+// info for a run of pixel to fill
+struct raster_info {
+ int startPix,endPix; // start and end pixel from the polygon POV
+ int sth,stv; // coordinates for the first pixel in the run, in (possibly another) POV
+ uint32_t* buffer; // pointer to the first pixel in the run
+};
+typedef void (*RasterInRunFunc) (raster_info &dest,void *data,int nst,float vst,int nen,float ven); // init for position ph,pv; the last parameter is a pointer
+
+
+enum Side {
+ LEFT = 0,
+ RIGHT = 1
+};
+
+enum FirstOrLast {
+ FIRST = 0,
+ LAST = 1
+};
+
+#endif
diff --git a/src/livarot/Path.cpp b/src/livarot/Path.cpp
new file mode 100644
index 0000000..352bc0a
--- /dev/null
+++ b/src/livarot/Path.cpp
@@ -0,0 +1,945 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * TODO: insert short description here
+ *//*
+ * Authors:
+ * see git history
+ * Fred
+ *
+ * Copyright (C) 2018 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <glib.h>
+#include "Path.h"
+
+#include <2geom/pathvector.h>
+
+#include "livarot/path-description.h"
+
+/*
+ * manipulation of the path data: path description and polyline
+ * grunt work...
+ * at the end of this file, 2 utilitary functions to get the point and tangent to path associated with a (command no;abcissis)
+ */
+
+
+Path::Path()
+{
+ descr_flags = 0;
+ pending_bezier_cmd = -1;
+ pending_moveto_cmd = -1;
+
+ back = false;
+}
+
+Path::~Path()
+{
+ for (auto & i : descr_cmd) {
+ delete i;
+ }
+}
+
+// debug function do dump the path contents on stdout
+void Path::Affiche()
+{
+ std::cout << "path: " << descr_cmd.size() << " commands." << std::endl;
+ for (auto i : descr_cmd) {
+ i->dump(std::cout);
+ std::cout << std::endl;
+ }
+
+ std::cout << std::endl;
+}
+
+void Path::Reset()
+{
+ for (auto & i : descr_cmd) {
+ delete i;
+ }
+
+ descr_cmd.clear();
+ pending_bezier_cmd = -1;
+ pending_moveto_cmd = -1;
+ descr_flags = 0;
+}
+
+void Path::Copy(Path * who)
+{
+ ResetPoints();
+
+ for (auto & i : descr_cmd) {
+ delete i;
+ }
+
+ descr_cmd.clear();
+
+ for (auto i : who->descr_cmd)
+ {
+ descr_cmd.push_back(i->clone());
+ }
+}
+
+void Path::CloseSubpath()
+{
+ descr_flags &= ~(descr_doing_subpath);
+ pending_moveto_cmd = -1;
+}
+
+int Path::ForcePoint()
+{
+ if (descr_flags & descr_adding_bezier) {
+ EndBezierTo ();
+ }
+
+ if ( (descr_flags & descr_doing_subpath) == 0 ) {
+ return -1;
+ }
+
+ if (descr_cmd.empty()) {
+ return -1;
+ }
+
+ descr_cmd.push_back(new PathDescrForced);
+ return descr_cmd.size() - 1;
+}
+
+
+void Path::InsertForcePoint(int at)
+{
+ if ( at < 0 || at > int(descr_cmd.size()) ) {
+ return;
+ }
+
+ if ( at == int(descr_cmd.size()) ) {
+ ForcePoint();
+ return;
+ }
+
+ descr_cmd.insert(descr_cmd.begin() + at, new PathDescrForced);
+}
+
+int Path::Close()
+{
+ if ( descr_flags & descr_adding_bezier ) {
+ CancelBezier();
+ }
+ if ( descr_flags & descr_doing_subpath ) {
+ CloseSubpath();
+ } else {
+ // Nothing to close.
+ return -1;
+ }
+
+ descr_cmd.push_back(new PathDescrClose);
+
+ descr_flags &= ~(descr_doing_subpath);
+ pending_moveto_cmd = -1;
+
+ return descr_cmd.size() - 1;
+}
+
+int Path::MoveTo(Geom::Point const &iPt)
+{
+ if ( descr_flags & descr_adding_bezier ) {
+ EndBezierTo(iPt);
+ }
+ if ( descr_flags & descr_doing_subpath ) {
+ CloseSubpath();
+ }
+ pending_moveto_cmd = descr_cmd.size();
+
+ descr_cmd.push_back(new PathDescrMoveTo(iPt));
+
+ descr_flags |= descr_doing_subpath;
+ return descr_cmd.size() - 1;
+}
+
+void Path::InsertMoveTo(Geom::Point const &iPt, int at)
+{
+ if ( at < 0 || at > int(descr_cmd.size()) ) {
+ return;
+ }
+
+ if ( at == int(descr_cmd.size()) ) {
+ MoveTo(iPt);
+ return;
+ }
+
+ descr_cmd.insert(descr_cmd.begin() + at, new PathDescrMoveTo(iPt));
+}
+
+int Path::LineTo(Geom::Point const &iPt)
+{
+ if (descr_flags & descr_adding_bezier) {
+ EndBezierTo (iPt);
+ }
+ if (!( descr_flags & descr_doing_subpath )) {
+ return MoveTo (iPt);
+ }
+
+ descr_cmd.push_back(new PathDescrLineTo(iPt));
+ return descr_cmd.size() - 1;
+}
+
+void Path::InsertLineTo(Geom::Point const &iPt, int at)
+{
+ if ( at < 0 || at > int(descr_cmd.size()) ) {
+ return;
+ }
+
+ if ( at == int(descr_cmd.size()) ) {
+ LineTo(iPt);
+ return;
+ }
+
+ descr_cmd.insert(descr_cmd.begin() + at, new PathDescrLineTo(iPt));
+}
+
+int Path::CubicTo(Geom::Point const &iPt, Geom::Point const &iStD, Geom::Point const &iEnD)
+{
+ if (descr_flags & descr_adding_bezier) {
+ EndBezierTo(iPt);
+ }
+ if ( (descr_flags & descr_doing_subpath) == 0) {
+ return MoveTo (iPt);
+ }
+
+ descr_cmd.push_back(new PathDescrCubicTo(iPt, iStD, iEnD));
+ return descr_cmd.size() - 1;
+}
+
+
+void Path::InsertCubicTo(Geom::Point const &iPt, Geom::Point const &iStD, Geom::Point const &iEnD, int at)
+{
+ if ( at < 0 || at > int(descr_cmd.size()) ) {
+ return;
+ }
+
+ if ( at == int(descr_cmd.size()) ) {
+ CubicTo(iPt,iStD,iEnD);
+ return;
+ }
+
+ descr_cmd.insert(descr_cmd.begin() + at, new PathDescrCubicTo(iPt, iStD, iEnD));
+}
+
+int Path::ArcTo(Geom::Point const &iPt, double iRx, double iRy, double angle,
+ bool iLargeArc, bool iClockwise)
+{
+ if (descr_flags & descr_adding_bezier) {
+ EndBezierTo(iPt);
+ }
+ if ( (descr_flags & descr_doing_subpath) == 0 ) {
+ return MoveTo(iPt);
+ }
+
+ descr_cmd.push_back(new PathDescrArcTo(iPt, iRx, iRy, angle, iLargeArc, iClockwise));
+ return descr_cmd.size() - 1;
+}
+
+
+void Path::InsertArcTo(Geom::Point const &iPt, double iRx, double iRy, double angle,
+ bool iLargeArc, bool iClockwise, int at)
+{
+ if ( at < 0 || at > int(descr_cmd.size()) ) {
+ return;
+ }
+
+ if ( at == int(descr_cmd.size()) ) {
+ ArcTo(iPt, iRx, iRy, angle, iLargeArc, iClockwise);
+ return;
+ }
+
+ descr_cmd.insert(descr_cmd.begin() + at, new PathDescrArcTo(iPt, iRx, iRy,
+ angle, iLargeArc, iClockwise));
+}
+
+int Path::TempBezierTo()
+{
+ if (descr_flags & descr_adding_bezier) {
+ CancelBezier();
+ }
+ if ( (descr_flags & descr_doing_subpath) == 0) {
+ // No starting point -> bad.
+ return -1;
+ }
+ pending_bezier_cmd = descr_cmd.size();
+
+ descr_cmd.push_back(new PathDescrBezierTo(Geom::Point(0, 0), 0));
+ descr_flags |= descr_adding_bezier;
+ descr_flags |= descr_delayed_bezier;
+ return descr_cmd.size() - 1;
+}
+
+void Path::CancelBezier()
+{
+ descr_flags &= ~(descr_adding_bezier);
+ descr_flags &= ~(descr_delayed_bezier);
+ if (pending_bezier_cmd < 0) {
+ return;
+ }
+
+ /* FIXME: I think there's a memory leak here */
+ descr_cmd.resize(pending_bezier_cmd);
+ pending_bezier_cmd = -1;
+}
+
+int Path::EndBezierTo()
+{
+ if (descr_flags & descr_delayed_bezier) {
+ CancelBezier ();
+ } else {
+ pending_bezier_cmd = -1;
+ descr_flags &= ~(descr_adding_bezier);
+ descr_flags &= ~(descr_delayed_bezier);
+ }
+ return -1;
+}
+
+int Path::EndBezierTo(Geom::Point const &iPt)
+{
+ if ( (descr_flags & descr_adding_bezier) == 0 ) {
+ return LineTo(iPt);
+ }
+ if ( (descr_flags & descr_doing_subpath) == 0 ) {
+ return MoveTo(iPt);
+ }
+ if ( (descr_flags & descr_delayed_bezier) == 0 ) {
+ return EndBezierTo();
+ }
+ PathDescrBezierTo *nData = dynamic_cast<PathDescrBezierTo *>(descr_cmd[pending_bezier_cmd]);
+ nData->p = iPt;
+ pending_bezier_cmd = -1;
+ descr_flags &= ~(descr_adding_bezier);
+ descr_flags &= ~(descr_delayed_bezier);
+ return -1;
+}
+
+
+int Path::IntermBezierTo(Geom::Point const &iPt)
+{
+ if ( (descr_flags & descr_adding_bezier) == 0 ) {
+ return LineTo (iPt);
+ }
+
+ if ( (descr_flags & descr_doing_subpath) == 0) {
+ return MoveTo (iPt);
+ }
+
+ descr_cmd.push_back(new PathDescrIntermBezierTo(iPt));
+
+ PathDescrBezierTo *nBData = dynamic_cast<PathDescrBezierTo *>(descr_cmd[pending_bezier_cmd]);
+ nBData->nb++;
+ return descr_cmd.size() - 1;
+}
+
+
+void Path::InsertIntermBezierTo(Geom::Point const &iPt, int at)
+{
+ if ( at < 0 || at > int(descr_cmd.size()) ) {
+ return;
+ }
+
+ if ( at == int(descr_cmd.size()) ) {
+ IntermBezierTo(iPt);
+ return;
+ }
+
+ descr_cmd.insert(descr_cmd.begin() + at, new PathDescrIntermBezierTo(iPt));
+}
+
+
+int Path::BezierTo(Geom::Point const &iPt)
+{
+ if ( descr_flags & descr_adding_bezier ) {
+ EndBezierTo(iPt);
+ }
+
+ if ( (descr_flags & descr_doing_subpath) == 0 ) {
+ return MoveTo (iPt);
+ }
+
+ pending_bezier_cmd = descr_cmd.size();
+
+ descr_cmd.push_back(new PathDescrBezierTo(iPt, 0));
+ descr_flags |= descr_adding_bezier;
+ descr_flags &= ~(descr_delayed_bezier);
+ return descr_cmd.size() - 1;
+}
+
+
+void Path::InsertBezierTo(Geom::Point const &iPt, int iNb, int at)
+{
+ if ( at < 0 || at > int(descr_cmd.size()) ) {
+ return;
+ }
+
+ if ( at == int(descr_cmd.size()) ) {
+ BezierTo(iPt);
+ return;
+ }
+
+ descr_cmd.insert(descr_cmd.begin() + at, new PathDescrBezierTo(iPt, iNb));
+}
+
+
+/*
+ * points of the polyline
+ */
+void
+Path::SetBackData (bool nVal)
+{
+ if (! back) {
+ if (nVal) {
+ back = true;
+ ResetPoints();
+ }
+ } else {
+ if (! nVal) {
+ back = false;
+ ResetPoints();
+ }
+ }
+}
+
+
+void Path::ResetPoints()
+{
+ pts.clear();
+}
+
+
+int Path::AddPoint(Geom::Point const &iPt, bool mvto)
+{
+ if (back) {
+ return AddPoint (iPt, -1, 0.0, mvto);
+ }
+
+ if ( !mvto && !pts.empty() && pts.back().p == iPt ) {
+ return -1;
+ }
+
+ int const n = pts.size();
+ pts.emplace_back(mvto ? polyline_moveto : polyline_lineto, iPt);
+ return n;
+}
+
+
+int Path::ReplacePoint(Geom::Point const &iPt)
+{
+ if (pts.empty()) {
+ return -1;
+ }
+
+ int const n = pts.size() - 1;
+ pts[n] = path_lineto(polyline_lineto, iPt);
+ return n;
+}
+
+
+int Path::AddPoint(Geom::Point const &iPt, int ip, double it, bool mvto)
+{
+ if (! back) {
+ return AddPoint (iPt, mvto);
+ }
+
+ if ( !mvto && !pts.empty() && pts.back().p == iPt ) {
+ return -1;
+ }
+
+ int const n = pts.size();
+ pts.emplace_back(mvto ? polyline_moveto : polyline_lineto, iPt, ip, it);
+ return n;
+}
+
+int Path::AddForcedPoint(Geom::Point const &iPt)
+{
+ if (back) {
+ return AddForcedPoint (iPt, -1, 0.0);
+ }
+
+ if ( pts.empty() || pts.back().isMoveTo != polyline_lineto ) {
+ return -1;
+ }
+
+ int const n = pts.size();
+ pts.emplace_back(polyline_forced, pts[n - 1].p);
+ return n;
+}
+
+
+int Path::AddForcedPoint(Geom::Point const &iPt, int /*ip*/, double /*it*/)
+{
+ /* FIXME: ip & it aren't used. Is this deliberate? */
+ if (!back) {
+ return AddForcedPoint (iPt);
+ }
+
+ if ( pts.empty() || pts.back().isMoveTo != polyline_lineto ) {
+ return -1;
+ }
+
+ int const n = pts.size();
+ pts.emplace_back(polyline_forced, pts[n - 1].p, pts[n - 1].piece, pts[n - 1].t);
+ return n;
+}
+
+void Path::PolylineBoundingBox(double &l, double &t, double &r, double &b)
+{
+ l = t = r = b = 0.0;
+ if ( pts.empty() ) {
+ return;
+ }
+
+ std::vector<path_lineto>::const_iterator i = pts.begin();
+ l = r = i->p[Geom::X];
+ t = b = i->p[Geom::Y];
+ ++i;
+
+ for (; i != pts.end(); ++i) {
+ r = std::max(r, i->p[Geom::X]);
+ l = std::min(l, i->p[Geom::X]);
+ b = std::max(b, i->p[Geom::Y]);
+ t = std::min(t, i->p[Geom::Y]);
+ }
+}
+
+
+/**
+ * \param piece Index of a one of our commands.
+ * \param at Distance along the segment that corresponds to `piece' (0 <= at <= 1)
+ * \param pos Filled in with the point at `at' on `piece'.
+ */
+
+void Path::PointAt(int piece, double at, Geom::Point &pos)
+{
+ if (piece < 0 || piece >= int(descr_cmd.size())) {
+ // this shouldn't happen: the piece we are asked for doesn't
+ // exist in the path
+ pos = Geom::Point(0,0);
+ return;
+ }
+
+ PathDescr const *theD = descr_cmd[piece];
+ int const typ = theD->getType();
+ Geom::Point tgt;
+ double len;
+ double rad;
+
+ if (typ == descr_moveto) {
+
+ return PointAt (piece + 1, 0.0, pos);
+
+ } else if (typ == descr_close || typ == descr_forced) {
+
+ return PointAt (piece - 1, 1.0, pos);
+
+ } else if (typ == descr_lineto) {
+
+ PathDescrLineTo const *nData = dynamic_cast<PathDescrLineTo const *>(theD);
+ TangentOnSegAt(at, PrevPoint (piece - 1), *nData, pos, tgt, len);
+
+ } else if (typ == descr_arcto) {
+
+ PathDescrArcTo const *nData = dynamic_cast<PathDescrArcTo const *>(theD);
+ TangentOnArcAt(at,PrevPoint (piece - 1), *nData, pos, tgt, len, rad);
+
+ } else if (typ == descr_cubicto) {
+
+ PathDescrCubicTo const *nData = dynamic_cast<PathDescrCubicTo const *>(theD);
+ TangentOnCubAt(at, PrevPoint (piece - 1), *nData, false, pos, tgt, len, rad);
+
+ } else if (typ == descr_bezierto || typ == descr_interm_bezier) {
+
+ int bez_st = piece;
+ while (bez_st >= 0) {
+ int nt = descr_cmd[bez_st]->getType();
+ if (nt == descr_bezierto)
+ break;
+ bez_st--;
+ }
+ if ( bez_st < 0 ) {
+ // Didn't find the beginning of the spline (bad).
+ // [pas trouvé le dubut de la spline (mauvais)]
+ return PointAt(piece - 1, 1.0, pos);
+ }
+
+ PathDescrBezierTo *stB = dynamic_cast<PathDescrBezierTo *>(descr_cmd[bez_st]);
+ if ( piece > bez_st + stB->nb ) {
+ // The spline goes past the authorized number of commands (bad).
+ // [la spline sort du nombre de commandes autorisé (mauvais)]
+ return PointAt(piece - 1, 1.0, pos);
+ }
+
+ int k = piece - bez_st;
+ Geom::Point const bStPt = PrevPoint(bez_st - 1);
+ if (stB->nb == 1 || k <= 0) {
+ PathDescrIntermBezierTo *nData = dynamic_cast<PathDescrIntermBezierTo *>(descr_cmd[bez_st + 1]);
+ TangentOnBezAt(at, bStPt, *nData, *stB, false, pos, tgt, len, rad);
+ } else {
+ // forcement plus grand que 1
+ if (k == 1) {
+ PathDescrIntermBezierTo *nextI = dynamic_cast<PathDescrIntermBezierTo *>(descr_cmd[bez_st + 1]);
+ PathDescrIntermBezierTo *nnextI = dynamic_cast<PathDescrIntermBezierTo *>(descr_cmd[bez_st + 2]);
+ PathDescrBezierTo fin(0.5 * (nextI->p + nnextI->p), 1);
+ TangentOnBezAt(at, bStPt, *nextI, fin, false, pos, tgt, len, rad);
+ } else if (k == stB->nb) {
+ PathDescrIntermBezierTo *nextI = dynamic_cast<PathDescrIntermBezierTo *>(descr_cmd[bez_st + k]);
+ PathDescrIntermBezierTo *prevI = dynamic_cast<PathDescrIntermBezierTo *>(descr_cmd[bez_st + k - 1]);
+ Geom::Point stP = 0.5 * ( prevI->p + nextI->p );
+ TangentOnBezAt(at, stP, *nextI, *stB, false, pos, tgt, len, rad);
+ } else {
+ PathDescrIntermBezierTo *nextI = dynamic_cast<PathDescrIntermBezierTo *>(descr_cmd[bez_st + k]);
+ PathDescrIntermBezierTo *prevI = dynamic_cast<PathDescrIntermBezierTo *>(descr_cmd[bez_st + k - 1]);
+ PathDescrIntermBezierTo *nnextI = dynamic_cast<PathDescrIntermBezierTo *>(descr_cmd[bez_st + k + 1]);
+ Geom::Point stP = 0.5 * ( prevI->p + nextI->p );
+ PathDescrBezierTo fin(0.5 * (nextI->p + nnextI->p), 1);
+ TangentOnBezAt(at, stP, *nextI, fin, false, pos, tgt, len, rad);
+ }
+ }
+ }
+}
+
+
+void Path::PointAndTangentAt(int piece, double at, Geom::Point &pos, Geom::Point &tgt)
+{
+ if (piece < 0 || piece >= int(descr_cmd.size())) {
+ // this shouldn't happen: the piece we are asked for doesn't exist in the path
+ pos = Geom::Point(0, 0);
+ return;
+ }
+
+ PathDescr const *theD = descr_cmd[piece];
+ int typ = theD->getType();
+ double len;
+ double rad;
+ if (typ == descr_moveto) {
+
+ return PointAndTangentAt(piece + 1, 0.0, pos, tgt);
+
+ } else if (typ == descr_close ) {
+
+ int cp = piece - 1;
+ while ( cp >= 0 && (descr_cmd[cp]->getType()) != descr_moveto ) {
+ cp--;
+ }
+ if ( cp >= 0 ) {
+ PathDescrMoveTo *nData = dynamic_cast<PathDescrMoveTo *>(descr_cmd[cp]);
+ PathDescrLineTo dst(nData->p);
+ TangentOnSegAt(at, PrevPoint (piece - 1), dst, pos, tgt, len);
+ }
+
+ } else if ( typ == descr_forced) {
+
+ return PointAndTangentAt(piece - 1, 1.0, pos,tgt);
+
+ } else if (typ == descr_lineto) {
+
+ PathDescrLineTo const *nData = dynamic_cast<PathDescrLineTo const *>(theD);
+ TangentOnSegAt(at, PrevPoint (piece - 1), *nData, pos, tgt, len);
+
+ } else if (typ == descr_arcto) {
+
+ PathDescrArcTo const *nData = dynamic_cast<PathDescrArcTo const *>(theD);
+ TangentOnArcAt (at,PrevPoint (piece - 1), *nData, pos, tgt, len, rad);
+
+ } else if (typ == descr_cubicto) {
+
+ PathDescrCubicTo const *nData = dynamic_cast<PathDescrCubicTo const *>(theD);
+ TangentOnCubAt (at, PrevPoint (piece - 1), *nData, false, pos, tgt, len, rad);
+
+ } else if (typ == descr_bezierto || typ == descr_interm_bezier) {
+ int bez_st = piece;
+ while (bez_st >= 0) {
+ int nt = descr_cmd[bez_st]->getType();
+ if (nt == descr_bezierto) break;
+ bez_st--;
+ }
+ if ( bez_st < 0 ) {
+ return PointAndTangentAt(piece - 1, 1.0, pos, tgt);
+ // Didn't find the beginning of the spline (bad).
+ // [pas trouvé le dubut de la spline (mauvais)]
+ }
+
+ PathDescrBezierTo* stB = dynamic_cast<PathDescrBezierTo*>(descr_cmd[bez_st]);
+ if ( piece > bez_st + stB->nb ) {
+ return PointAndTangentAt(piece - 1, 1.0, pos, tgt);
+ // The spline goes past the number of authorized commands (bad).
+ // [la spline sort du nombre de commandes autorisé (mauvais)]
+ }
+
+ int k = piece - bez_st;
+ Geom::Point const bStPt(PrevPoint( bez_st - 1 ));
+ if (stB->nb == 1 || k <= 0) {
+ PathDescrIntermBezierTo* nData = dynamic_cast<PathDescrIntermBezierTo *>(descr_cmd[bez_st + 1]);
+ TangentOnBezAt (at, bStPt, *nData, *stB, false, pos, tgt, len, rad);
+ } else {
+ // forcement plus grand que 1
+ if (k == 1) {
+ PathDescrIntermBezierTo *nextI = dynamic_cast<PathDescrIntermBezierTo *>(descr_cmd[bez_st + 1]);
+ PathDescrIntermBezierTo *nnextI = dynamic_cast<PathDescrIntermBezierTo *>(descr_cmd[bez_st + 2]);
+ PathDescrBezierTo fin(0.5 * (nextI->p + nnextI->p), 1);
+ TangentOnBezAt(at, bStPt, *nextI, fin, false, pos, tgt, len, rad);
+ } else if (k == stB->nb) {
+ PathDescrIntermBezierTo *prevI = dynamic_cast<PathDescrIntermBezierTo *>(descr_cmd[bez_st + k - 1]);
+ PathDescrIntermBezierTo *nextI = dynamic_cast<PathDescrIntermBezierTo *>(descr_cmd[bez_st + k]);
+ Geom::Point stP = 0.5 * ( prevI->p + nextI->p );
+ TangentOnBezAt(at, stP, *nextI, *stB, false, pos, tgt, len, rad);
+ } else {
+ PathDescrIntermBezierTo *prevI = dynamic_cast<PathDescrIntermBezierTo *>(descr_cmd[bez_st + k - 1]);
+ PathDescrIntermBezierTo *nextI = dynamic_cast<PathDescrIntermBezierTo *>(descr_cmd[bez_st + k]);
+ PathDescrIntermBezierTo *nnextI = dynamic_cast<PathDescrIntermBezierTo *>(descr_cmd[bez_st + k + 1]);
+ Geom::Point stP = 0.5 * ( prevI->p + nextI->p );
+ PathDescrBezierTo fin(0.5 * (nnextI->p + nnextI->p), 1);
+ TangentOnBezAt(at, stP, *nextI, fin, false, pos, tgt, len, rad);
+ }
+ }
+ }
+}
+
+/**
+ * Apply a transform in-place.
+ *
+ * Note: Converts to Geom::PathVector, applies the transform, and converts back.
+ */
+void Path::Transform(const Geom::Affine &trans)
+{
+ LoadPathVector(MakePathVector() * trans);
+}
+
+void Path::FastBBox(double &l,double &t,double &r,double &b)
+{
+ l = t = r = b = 0;
+ bool empty = true;
+ Geom::Point lastP(0, 0);
+
+ for (auto & i : descr_cmd) {
+ int const typ = i->getType();
+ switch ( typ ) {
+ case descr_lineto:
+ {
+ PathDescrLineTo *nData = dynamic_cast<PathDescrLineTo *>(i);
+ if ( empty ) {
+ l = r = nData->p[Geom::X];
+ t = b = nData->p[Geom::Y];
+ empty = false;
+ } else {
+ if ( nData->p[Geom::X] < l ) {
+ l = nData->p[Geom::X];
+ }
+ if ( nData->p[Geom::X] > r ) {
+ r = nData->p[Geom::X];
+ }
+ if ( nData->p[Geom::Y] < t ) {
+ t = nData->p[Geom::Y];
+ }
+ if ( nData->p[Geom::Y] > b ) {
+ b = nData->p[Geom::Y];
+ }
+ }
+ lastP = nData->p;
+ }
+ break;
+
+ case descr_moveto:
+ {
+ PathDescrMoveTo *nData = dynamic_cast<PathDescrMoveTo *>(i);
+ if ( empty ) {
+ l = r = nData->p[Geom::X];
+ t = b = nData->p[Geom::Y];
+ empty = false;
+ } else {
+ if ( nData->p[Geom::X] < l ) {
+ l = nData->p[Geom::X];
+ }
+ if ( nData->p[Geom::X] > r ) {
+ r = nData->p[Geom::X];
+ }
+ if ( nData->p[Geom::Y] < t ) {
+ t = nData->p[Geom::Y];
+ }
+ if ( nData->p[Geom::Y] > b ) {
+ b = nData->p[Geom::Y];
+ }
+ }
+ lastP = nData->p;
+ }
+ break;
+
+ case descr_arcto:
+ {
+ PathDescrArcTo *nData = dynamic_cast<PathDescrArcTo *>(i);
+ if ( empty ) {
+ l = r = nData->p[Geom::X];
+ t = b = nData->p[Geom::Y];
+ empty = false;
+ } else {
+ if ( nData->p[Geom::X] < l ) {
+ l = nData->p[Geom::X];
+ }
+ if ( nData->p[Geom::X] > r ) {
+ r = nData->p[Geom::X];
+ }
+ if ( nData->p[Geom::Y] < t ) {
+ t = nData->p[Geom::Y];
+ }
+ if ( nData->p[Geom::Y] > b ) {
+ b = nData->p[Geom::Y];
+ }
+ }
+ lastP = nData->p;
+ }
+ break;
+
+ case descr_cubicto:
+ {
+ PathDescrCubicTo *nData = dynamic_cast<PathDescrCubicTo *>(i);
+ if ( empty ) {
+ l = r = nData->p[Geom::X];
+ t = b = nData->p[Geom::Y];
+ empty = false;
+ } else {
+ if ( nData->p[Geom::X] < l ) {
+ l = nData->p[Geom::X];
+ }
+ if ( nData->p[Geom::X] > r ) {
+ r = nData->p[Geom::X];
+ }
+ if ( nData->p[Geom::Y] < t ) {
+ t = nData->p[Geom::Y];
+ }
+ if ( nData->p[Geom::Y] > b ) {
+ b = nData->p[Geom::Y];
+ }
+ }
+
+/* bug 249665: "...the calculation of the bounding-box for cubic-paths
+has some extra steps to make it work correctly in Win32 that unfortunately
+are unnecessary in Linux, generating wrong results. This only shows in
+Type1 fonts because they use cubic-paths instead of the
+bezier-paths used by True-Type fonts."
+*/
+
+#ifdef _WIN32
+ Geom::Point np = nData->p - nData->end;
+ if ( np[Geom::X] < l ) {
+ l = np[Geom::X];
+ }
+ if ( np[Geom::X] > r ) {
+ r = np[Geom::X];
+ }
+ if ( np[Geom::Y] < t ) {
+ t = np[Geom::Y];
+ }
+ if ( np[Geom::Y] > b ) {
+ b = np[Geom::Y];
+ }
+
+ np = lastP + nData->start;
+ if ( np[Geom::X] < l ) {
+ l = np[Geom::X];
+ }
+ if ( np[Geom::X] > r ) {
+ r = np[Geom::X];
+ }
+ if ( np[Geom::Y] < t ) {
+ t = np[Geom::Y];
+ }
+ if ( np[Geom::Y] > b ) {
+ b = np[Geom::Y];
+ }
+#endif
+
+ lastP = nData->p;
+ }
+ break;
+
+ case descr_bezierto:
+ {
+ PathDescrBezierTo *nData = dynamic_cast<PathDescrBezierTo *>(i);
+ if ( empty ) {
+ l = r = nData->p[Geom::X];
+ t = b = nData->p[Geom::Y];
+ empty = false;
+ } else {
+ if ( nData->p[Geom::X] < l ) {
+ l = nData->p[Geom::X];
+ }
+ if ( nData->p[Geom::X] > r ) {
+ r = nData->p[Geom::X];
+ }
+ if ( nData->p[Geom::Y] < t ) {
+ t = nData->p[Geom::Y];
+ }
+ if ( nData->p[Geom::Y] > b ) {
+ b = nData->p[Geom::Y];
+ }
+ }
+ lastP = nData->p;
+ }
+ break;
+
+ case descr_interm_bezier:
+ {
+ PathDescrIntermBezierTo *nData = dynamic_cast<PathDescrIntermBezierTo *>(i);
+ if ( empty ) {
+ l = r = nData->p[Geom::X];
+ t = b = nData->p[Geom::Y];
+ empty = false;
+ } else {
+ if ( nData->p[Geom::X] < l ) {
+ l = nData->p[Geom::X];
+ }
+ if ( nData->p[Geom::X] > r ) {
+ r = nData->p[Geom::X];
+ }
+ if ( nData->p[Geom::Y] < t ) {
+ t = nData->p[Geom::Y];
+ }
+ if ( nData->p[Geom::Y] > b ) {
+ b = nData->p[Geom::Y];
+ }
+ }
+ }
+ break;
+ }
+ }
+}
+
+char *Path::svg_dump_path() const
+{
+ Inkscape::SVGOStringStream os;
+
+ for (int i = 0; i < int(descr_cmd.size()); i++) {
+ Geom::Point const p = (i == 0) ? Geom::Point(0, 0) : PrevPoint(i - 1);
+ descr_cmd[i]->dumpSVG(os, p);
+ }
+
+ return g_strdup (os.str().c_str());
+}
+
+// Find out if the segment that corresponds to 'piece' is a straight line
+bool Path::IsLineSegment(int piece)
+{
+ if (piece < 0 || piece >= int(descr_cmd.size())) {
+ return false;
+ }
+
+ PathDescr const *theD = descr_cmd[piece];
+ int const typ = theD->getType();
+
+ return (typ == descr_lineto);
+}
+
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/livarot/Path.h b/src/livarot/Path.h
new file mode 100644
index 0000000..f61451d
--- /dev/null
+++ b/src/livarot/Path.h
@@ -0,0 +1,1001 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * TODO: insert short description here
+ *//*
+ * Authors: see git history
+ *
+ * Copyright (C) 2014 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+/*
+ * Path.h
+ * nlivarot
+ *
+ * Created by fred on Tue Jun 17 2003.
+ *
+ */
+
+#ifndef my_path
+#define my_path
+
+#include <vector>
+#include "LivarotDefs.h"
+#include <2geom/point.h>
+
+struct PathDescr;
+struct PathDescrLineTo;
+struct PathDescrArcTo;
+struct PathDescrCubicTo;
+struct PathDescrBezierTo;
+struct PathDescrIntermBezierTo;
+
+class SPStyle;
+
+/*
+ * the Path class: a structure to hold path description and their polyline approximation (not kept in sync)
+ * the path description is built with regular commands like MoveTo() LineTo(), etc
+ * the polyline approximation is built by a call to Convert() or its variants
+ * another possibility would be to call directly the AddPoint() functions, but that is not encouraged
+ * the conversion to polyline can salvage data as to where on the path each polyline's point lies; use
+ * ConvertWithBackData() for this. after this call, it's easy to rewind the polyline: sequences of points
+ * of the same path command can be reassembled in a command
+ */
+
+// polyline description commands
+enum
+{
+ polyline_lineto = 0, // a lineto
+ polyline_moveto = 1, // a moveto
+ polyline_forced = 2 // a forced point, ie a point that was an angle or an intersection in a previous life
+ // or more realistically a control point in the path description that created the polyline
+ // forced points are used as "breakable" points for the polyline -> cubic bezier patch operations
+ // each time the bezier fitter encounters such a point in the polyline, it decreases its treshhold,
+ // so that it is more likely to cut the polyline at that position and produce a bezier patch
+};
+
+class Shape;
+
+// path creation: 2 phases: first the path is given as a succession of commands (MoveTo, LineTo, CurveTo...); then it
+// is converted in a polyline
+// a polylone can be stroked or filled to make a polygon
+
+/**
+ * Path and its polyline approximation.
+ *
+ * A Path is exactly analogous to an SVG path element. Like the SVG path element, this class
+ * stores path commands. A Path can be approximated by line segments and this approximation
+ * is known as a "polyline approximation". Internally, the polyline approximation is stored
+ * as a set of points.
+ *
+ * Each path command (except the MoveTo), creates a new segment. A path segment can be defined
+ * as a function of time over the interval [0, 1]. Each point in the polyline approximation can
+ * store the index of the path command that created the path segment that it came from and the time
+ * value at which it existed. The midpoint of a line segment would be at \f[ t = 0.5 \f] for
+ * example. This information is known as "back data" since it preserves the information about the
+ * original segments that existed in the path and can help us recreate them or their portions back.
+ * Note that the first point of a subpath stores the index of the moveTo command.
+ *
+ * To use this class create a new instance. Call the command functions such as Path::MoveTo,
+ * Path::LineTo, Path::CubicTo, etc to append path commands. Then call one of Path::Convert,
+ * Path::ConvertEvenLines or Path::ConvertWithBackData to generate the polyline approximation.
+ * Then you can do simplification by calling Path::Simplify or fill a Shape by calling Path::Fill
+ * on the shape to use features such as Offsetting, Boolean Operations and Tweaking.
+ *
+ * Path *path = new Path;
+ * path->MoveTo(Geom::Point(10, 10));
+ * path->LineTo(Geom::Point(100, 10));
+ * path->LineTo(Geom::Point(100, 100));
+ * path->Close();
+ * path->ConvertEvenLines(0.001); // You can use the other variants too
+ * // insteresting stuff here
+ *
+ */
+class Path
+{
+ friend class Shape;
+
+public:
+
+ // flags for the path construction
+ enum
+ {
+ descr_ready = 0,
+ descr_adding_bezier = 1, // we're making a bezier spline, so you can expect pending_bezier_* to have a value
+ descr_doing_subpath = 2, // we're doing a path, so there is a moveto somewhere
+ descr_delayed_bezier = 4,// the bezier spline we're doing was initiated by a TempBezierTo(), so we'll need an endpoint
+ descr_dirty = 16 // the path description was modified
+ };
+
+ // some data for the construction: what's pending, and some flags
+ int descr_flags;
+ int pending_bezier_cmd;
+ int pending_bezier_data;
+ int pending_moveto_cmd;
+ int pending_moveto_data;
+
+ std::vector<PathDescr*> descr_cmd; /*!< A vector of owned pointers to path commands. */
+
+ /**
+ * Points of the polyline approximation.
+ *
+ * Since the polyline approximation approximates a Path which can have multiple subpaths, the
+ * approximation can also have a set of continuous polylines.
+ */
+ struct path_lineto
+ {
+ path_lineto(bool m, Geom::Point pp) : isMoveTo(m), p(pp), piece(-1), t(0), closed(false) {}
+ path_lineto(bool m, Geom::Point pp, int pie, double tt) : isMoveTo(m), p(pp), piece(pie), t(tt), closed(false) {}
+
+ int isMoveTo; /*!< A flag that stores one of polyline_lineto, polyline_moveto, polyline_forced */
+ Geom::Point p; /*!< The point itself. */
+ int piece; /*!< Index of the path command that created the path segment that this point comes from.*/
+ double t; /*!< The time at which this point exists in the path segment. A value between 0 and 1. */
+ bool closed; /*!< True indicates that subpath is closed (this point is the last point of a closed subpath) */
+ };
+
+ std::vector<path_lineto> pts; /*!< A vector storing the polyline approximation points. */
+
+ bool back; /*!< If true, indicates that the polyline approximation is going to have backdata.
+ No need to set this manually though. When Path::Convert or any of its variants is called, it's set automatically. */
+
+ Path();
+ virtual ~Path();
+
+ // creation of the path description
+
+ /**
+ * Clears all stored path commands and resets flags that are used by command functions while adding path
+ * commands.
+ */
+ void Reset();
+
+ /**
+ * Clear all stored path commands, resets flags and imports path commands from the passed Path
+ * object.
+ *
+ * @param who Path object whose path commands to copy.
+ */
+ void Copy (Path * who);
+
+ /**
+ * Appends a forced point path command.
+ *
+ * Forced points are places in the path which are preferred to be kept in the simplification
+ * algorithm. The simplification algorithm will try to retain those points. This can be beneficial
+ * in situations such as self-intersections where we would want the intersection point to remain
+ * unchanged after any simplification is done.
+ *
+ * TODO: Confirm this with some testing.
+ *
+ * A forced point command can't be appended if there is no active subpath that we are drawing on.
+ * If you imagine calling these command functions as giving instructions to a pen, a forced point
+ * command requires that the pen is already touching the canvas. The pen is not on the canvas
+ * when you instantiate the Path object and it also leaves it when you call Path::Close. The term
+ * "active subpath" simply means that the pen is already touching the canvas.
+ *
+ * @return Index of the path command in the path commands array if it got appended, -1 otherwise.
+ */
+ int ForcePoint();
+
+ /**
+ * Appends a close path command.
+ *
+ * Close path command can't be appended if there is no acive subpath.
+ *
+ * @return Index of the path command in the path commands array if it got appended, -1 otherwise.
+ */
+ int Close();
+
+ /** Appends a MoveTo path command.
+ *
+ * @param ip The point to move to.
+ *
+ * @return The index of the path description added.
+ */
+ int MoveTo ( Geom::Point const &ip);
+
+ /** Appends a LineTo path command.
+ *
+ * @param ip The point to draw a line to.
+ *
+ * @return The index of the path description added.
+ */
+ int LineTo ( Geom::Point const &ip);
+
+ /**
+ * Appends a CubicBezier path command.
+ *
+ * In order to understand the parameters let p0, p1, p2, p3 denote the four points of a
+ * cubic Bezier curve. p0 is the start point. p3 is the end point. p1 and p2 are the
+ * two control points.
+ *
+ * @param ip The final point of the bezier curve or p3.
+ * @param iStD 3 * (p1 - p0). Weird way to store it but that's how it is.
+ * @param iEnD 3 * (p3 - p2). Weird way to store it but that's how it is.
+ *
+ * @return The index of the path description added.
+ */
+ int CubicTo ( Geom::Point const &ip, Geom::Point const &iStD, Geom::Point const &iEnD);
+
+ /**
+ * Appends an ArcTo path command.
+ *
+ * The parameters are identical to the SVG elliptical arc command.
+ *
+ * @param ip The final point of the arc.
+ * @param iRx The radius in the x direction.
+ * @param iRy The radius in the y direction.
+ * @param angle The angle w.r.t x axis in degrees. TODO: Confirm this
+ * @param iLargeArc If true, it's the larger arc, if false, it's the smaller one.
+ * @param iClockwise If true, it's the clockwise arc, if false, it's the anti-clockwise one.
+ *
+ * @return The index of the path description added.
+ */
+ int ArcTo ( Geom::Point const &ip, double iRx, double iRy, double angle, bool iLargeArc, bool iClockwise);
+
+ /**
+ * Adds a control point for the last quadratic bezier spline command.
+ *
+ * Adds a control point to the quadratic bezier spline that was last inserted with a call to
+ * Path::BezierTo.
+ *
+ * @param ip The control point.
+ *
+ * @return The index of the path description added.
+ */
+ int IntermBezierTo ( Geom::Point const &ip); // add a quadratic bezier spline control point
+
+ /**
+ * Appends a quadratic bezier spline path command.
+ *
+ * A quadratic bezier spline is basically a set of quadratic bezier curves. To simply illustrate
+ * how this spline is made up, let's define some terms first. Let midpoint(a, b) represent the
+ * midpoint of the points a and b. Let quad(a, b, c) represent a quadratic Bezier curve with a
+ * as the start point, b as the control point and c as the end point.
+ *
+ * Given a set of points: st, p1, p2, p3, p4, en where st and en are the endpoints and the rest
+ * are control points, we will have the following quadratic Bezier curves connected end to end.
+ *
+ * quad(st, p1, midpoint(p1, p2))
+ * quad(midpoint(p1, p2), p2, midpoint(p2, p3))
+ * quad(midpoint(p2, p3), p3, midpoint(p3, p4))
+ * quad(midpoint(p3, p4), p4, en)
+ *
+ * No need to specify the number of control points. That'll be done automatically as you call
+ * Path::IntermBezierTo to add the control points. The sequence of instructions are like:
+ * 1. Call Path::BezierTo with the final point.
+ * 2. Call Path::IntermBezierTo with control points. One call for each control point.
+ * 3. Call Path::EndBezierTo to mark the end of the quadratic bezier spline command.
+ *
+ * Basically, the interface has been designed in such a way that you specify the final point and
+ * then add control points one by one as many as you like. Once you're done, you call
+ * Path::EndBezierTo to inform that you're done adding points for the spline.
+ *
+ * @param ip The final point of the quadratic bezier spline.
+ *
+ * @return The index of the path description added.
+ */
+ int BezierTo ( Geom::Point const &ip); // quadratic bezier spline to this point (control points can be added after this)
+
+ /**
+ * Finish any ongoing BezierTo instruction.
+ *
+ * Once Path::BezierTo has been called, the object expects you to specify control points by
+ * calling Path::IntermBezierTo for each control point. Once you're done specifying the control
+ * points you call Path::EndBezierTo to finish the quadratic bezier spline.
+ *
+ * @return -1 all the time.
+ */
+ int EndBezierTo();
+
+ /**
+ * Appends a quadratic bezier spline path command (without specifying a final point).
+ *
+ * If you use Path::BezierTo, you have to specify the final point of the spline first and then
+ * follow it with all the control points. However, this is kinda counter-intuitive. Visually, we
+ * would look at the control points first and then the final end point. This function allows a
+ * similar mechanism. You can start a quadratic bezier spline without mentioning any final point,
+ * specify as many control points as you like and then while finishing it, you can specify the
+ * final point of the spline.
+ *
+ * The sequence of instructions would be:
+ * 1. Path::TempBezierTo to start.
+ * 2. Path::IntermBezierTo to specify control points. One call for each control point.
+ * 3. Path::EndBezierTo(Geom::Point const&) passing the final point of the quadratic bezier spline and finish the
+ * quadratic bezier spline command.
+ *
+ * @return Index of the description added.
+ */
+ int TempBezierTo(); // start a quadratic bezier spline (control points can be added after this)
+
+ /**
+ * Finish any ongoing TempBezierTo instruction.
+ *
+ * Used to specify the final point of a quadratic bezier spline which was started by calling
+ * Path::TempBezierTo.
+ *
+ * @param ip The final point.
+ *
+ * @return -1 all the time.
+ */
+ int EndBezierTo ( Geom::Point const &ip); // ends a quadratic bezier spline (for curves started with TempBezierTo)
+
+ // transforms a description in a polyline (for stroking and filling)
+ // treshhold is the max length^2 (sort of)
+
+ /**
+ * Creates a polyline approximation of the path. Doesn't keep any back data. Line segments are
+ * not split into smaller line segments.
+ *
+ * Threshold has no strict definition. It means different things for each path segment.
+ *
+ * @param threshhold The error threshold used to approximate curves by line segments. The smaller
+ * this is, the more line segments there will be.
+ */
+ void Convert (double treshhold);
+
+ /**
+ * Creates a polyline approximation of the path. Line segments are split into further smaller line segments
+ * such that each of those line segments is no bigger than threshold.
+ *
+ * Breaking up into further smaller line segments is useful for path simplification as you can
+ * then fit cubic Bezier patches on those small line segments.
+ *
+ * Threshold has no strict definition. It means different things for each path segment.
+ *
+ * @param threshhold The error threshold used to approximate the path. The smaller this is, the
+ * more line segments there will be and the better the polyline approximation would be.
+ */
+ void ConvertEvenLines (double treshhold); // decomposes line segments too, for later recomposition
+
+ /**
+ * Creates a polyline approximation of the path. Line segments are
+ * not split into smaller line segments. Stores back data for later recomposition.
+ *
+ * Threshold has no strict definition. It means different things for each path segment.
+ *
+ * @param threshhold The error threshold used to approximate the path. The smaller this is, the
+ * more line segments there will be and the better the polyline approximation would be.
+ */
+ void ConvertWithBackData (double treshhold);
+
+ // creation of the polyline (you can tinker with these function if you want)
+
+ /**
+ * Sets the back variable to the value passed in and clears the polyline approximation.
+ *
+ * @param nVal True if we are going to have backdata and false otherwise.
+ */
+ void SetBackData (bool nVal); // has back data?
+
+ /**
+ * Clears the polyline approximation.
+ */
+ void ResetPoints(); // resets to the empty polyline
+
+ /**
+ * Adds a point to the polyline approximation's list of points.
+ *
+ * This is used internally by Path::Convert and its variants, so you'd not need to use it by
+ * yourself.
+ *
+ * If back variable of the instance is set to true, dummy back data will be used with the point.
+ * Piece being -1 and time being 0. Since this function doesn't take any back data you'll have to
+ * fill in something.
+ *
+ * The point doesn't get added if it's a lineto and the point before it has the same coordinates.
+ *
+ * @param iPt The point itself.
+ * @param mvto If true, it's a moveTo otherwise it's a lineto.
+ *
+ * @return Index of the point added if it was added, -1 otherwise.
+ */
+ int AddPoint ( Geom::Point const &iPt, bool mvto = false); // add point
+
+ /**
+ * Adds a point to the polyline approximation's list of points. Let's you specify back data.
+ *
+ * This is used internally by Path::Convert and its variants, so you'd not need to use it by
+ * yourself.
+ *
+ * @param iPt The point itself.
+ * @param ip The index of the path command that created the segment that this point belongs to.
+ * @param it The time in that path segment at which this point exists. 0 is beginning and 1
+ * is end.
+ * @param mvto If true, it's a moveTo otherwise it's a lineto.
+ *
+ * The point doesn't get added if it's a lineto and the point before it has the same coordinates.
+ *
+ * @return Index of the point added if it was added, -1 otherwise.
+ */
+ int AddPoint ( Geom::Point const &iPt, int ip, double it, bool mvto = false);
+
+ /**
+ * Adds a forced point to the polyline approximation's list of points without specifying any back data.
+ *
+ * The argument of this function is useless. The point that gets added as a forced point has the
+ * same coordinates as the last point that was added. If no points exist or the last one isn't a
+ * lineto, nothing gets added.
+ *
+ * Dummy back data will be used if the back variable of the instance is true.
+ *
+ * @param iPt Unused argument.
+ *
+ * @return Index of the point added if it was added, -1 otherwise.
+ */
+ int AddForcedPoint ( Geom::Point const &iPt); // add point
+
+ /**
+ * Add a forced point to the polyline approximation's list of points while specifying backdata.
+ *
+ * The argument of this function is useless. The point that gets added as a forced point has the
+ * same coordinates as the last point that was added. If no points exist or the last one isn't a
+ * lineto, nothing gets added. The back data is also picked up from the last point that was
+ * added.
+ *
+ * @param iPt Unused argument.
+ * @param ip Unused argument.
+ * @param it Unused argument.
+ *
+ * @return Index of the point added if it was added, -1 otherwise.
+ */
+ int AddForcedPoint ( Geom::Point const &iPt, int ip, double it);
+
+ /**
+ * Replace the last point in the polyline approximation's list of points with the passed one.
+ *
+ * Nothing gets added if no points exist already.
+ *
+ * @param iPt The point to replace the last one with.
+ *
+ * @return Index of the last point added if it was added, -1 otherwise.
+ */
+ int ReplacePoint(Geom::Point const &iPt); // replace point
+
+ // transform in a polygon (in a graph, in fact; a subsequent call to ConvertToShape is needed)
+ // - fills the polyline; justAdd=true doesn't reset the Shape dest, but simply adds the polyline into it
+ // closeIfNeeded=false prevent the function from closing the path (resulting in a non-eulerian graph
+ // pathID is a identification number for the path, and is used for recomposing curves from polylines
+ // give each different Path a different ID, and feed the appropriate orig[] to the ConvertToForme() function
+
+ /**
+ * Fills the shape with the polyline approximation stored in this object.
+ *
+ * For each line segment in the polyline approximation, an edge is created in the shape.
+ *
+ * One important point to highlight is the closeIfNeeded argument. For each subpath (where a
+ * sub path is a moveTo followed by one or more lineTo points) you can either have the start and end
+ * points being identical or very close (a closed contour) or have them apart (an open contour).
+ * If you set closeIfNeeded to true, it'll automatically add a closing segment if needed and
+ * close an open contour by itself. If your contour is already closed, it makes sure that the
+ * first and last point are the same ones in the graph (instead of being two indentical points).
+ * If closeIfNeeded is false, it just doesn't care at all. Even if your contour is closed, the
+ * first and last point will be separate (even though they would be duplicates).
+ *
+ * @param dest The shape to fill.
+ * @param pathID A unique number for this path. The shape will associate this number with each
+ * edge that comes from this path. Later on, when you use Shape::ConvertToForme you'll pass an array
+ * of Path objects (named orig) and the shape will use that pathID to do orig[pathID] and get the
+ * original path information.
+ * @param justAdd If set to true, this will function will just fill stuff in without resetting
+ * any existing stuff in Shape. If set to false, it'll make sure to reset the shape and already
+ * make room for the maximum number of possible points and edges.
+ * @param closeIfNeeded If set to true, the graph will be closed always. Otherwise, it won't be
+ * closed.
+ * @param invert If set to true, the graph is drawn exactly in the manner opposite to the actual
+ * polyline approximation that this object stores, if false, it's stored indentical to how it's
+ * in the polyline approximation.
+ *
+ * @todo "the graph is drawn exactly in the manner opposite"? Does this mean the edges of the
+ * directed graph are reversed?
+ */
+ void Fill(Shape *dest, int pathID = -1, bool justAdd = false,
+ bool closeIfNeeded = true, bool invert = false);
+
+ // - stroke the path; usual parameters: type of cap=butt, type of join=join and miter (see LivarotDefs.h)
+ // doClose treat the path as closed (ie a loop)
+ void Stroke(Shape *dest, bool doClose, double width, JoinType join,
+ ButtType butt, double miter, bool justAdd = false);
+
+ // build a Path that is the outline of the Path instance's description (the result is stored in dest)
+ // it doesn't compute the exact offset (it's way too complicated, but an approximation made of cubic bezier patches
+ // and segments. the algorithm was found in a plugin for Impress (by Chris Cox), but i can't find it back...
+ void Outline(Path *dest, double width, JoinType join, ButtType butt,
+ double miter);
+
+ // half outline with edges having the same direction as the original
+ void OutsideOutline(Path *dest, double width, JoinType join, ButtType butt,
+ double miter);
+
+ // half outline with edges having the opposite direction as the original
+ void InsideOutline (Path * dest, double width, JoinType join, ButtType butt,
+ double miter);
+
+ // polyline to cubic bezier patches
+
+ /**
+ * Simplify the path.
+ *
+ * Fit the least possible number of cubic Bezier patches on the polyline approximation while
+ * respecting the threshold (keeping the error small). The function clears existing path commands
+ * and the resulting cubic Bezier patches will be pushed as path commands in the instance.
+ *
+ * The algorithm to fit cubic Bezier curves on the polyline approximation's points.
+ *
+ * http://www.cs.mtu.edu/~shene/COURSES/cs3621/NOTES/INT-APP/CURVE-APP-global.html
+ *
+ * @param threshold The threshold for simplification. A measure of how much error is okay. The
+ * smaller this number is, the more conservative the fitting algorithm will be.
+ */
+ void Simplify (double treshhold);
+
+ /**
+ * Simplify the path with a different approach.
+ *
+ * This function is also supposed to do simplification but by merging (coalescing) existing
+ * path descriptions instead of doing any fitting. But I seriously doubt whether this is useful
+ * at all or works at all. More experimentation needed. TODO
+ *
+ * @param threshold The threshold for simplification.
+ */
+ void Coalesce (double tresh);
+
+ // utilities
+ // piece is a command no in the command list
+ // "at" is an abcissis on the path portion associated with this command
+ // 0=beginning of portion, 1=end of portion.
+ void PointAt (int piece, double at, Geom::Point & pos);
+ void PointAndTangentAt (int piece, double at, Geom::Point & pos, Geom::Point & tgt);
+
+ // last control point before the command i (i included)
+ // used when dealing with quadratic bezier spline, cause these can contain arbitrarily many commands
+ const Geom::Point PrevPoint (const int i) const;
+
+ // dash the polyline
+ // the result is stored in the polyline, so you lose the original. make a copy before if needed
+ void DashPolyline(float head,float tail,float body,int nbD, const float dashs[],bool stPlain,float stOffset);
+
+ void DashPolylineFromStyle(SPStyle *style, float scale, float min_len);
+
+ //utilitaire pour inkscape
+
+ /**
+ * Load a lib2geom Geom::Path in this path object.
+ *
+ * The Geom::Path object is read and path commands making it up are appended in the Path object.
+ *
+ * @param path The Geom::Path object to load.
+ * @param tr A transformation matrix.
+ * @param doTransformation If set to true, the transformation matrix tr is applied on the path
+ * before it's loaded in this path object.
+ * @param append If set to true, any existing path commands in this object are retained. If
+ * set to false, any existing path commands will be cleared.
+ */
+ void LoadPath(Geom::Path const &path, Geom::Affine const &tr, bool doTransformation, bool append = false);
+
+ /**
+ * Load a lib2geom Geom::PathVector in this path object. (supports transformation)
+ *
+ * Any existing path commands in this object are not cleared.
+ *
+ * @param pv The Geom::PathVector object to load.
+ * @param tr A transformation to apply on each path.
+ * @param doTransformation If set to true, the transformation in tr is applied.
+ */
+ void LoadPathVector(Geom::PathVector const &pv, Geom::Affine const &tr, bool doTransformation);
+
+ /**
+ * Load a lib2geom Geom::PathVector in this path object.
+ *
+ * Any existing path commands in this object are not cleared.
+ *
+ * @param pv A reference to the Geom::PathVector object to load.
+ */
+ void LoadPathVector(Geom::PathVector const &pv);
+
+ /**
+ * Create a lib2geom Geom::PathVector from this Path object.
+ *
+ * Looks like the time this was written Geom::PathBuilder didn't exist or maybe
+ * the author wasn't aware of it.
+ *
+ * @return The Geom::PathVector created.
+ */
+ Geom::PathVector MakePathVector();
+
+ /**
+ * Apply a transformation on all path commands.
+ *
+ * Done by calling the transform method on each path command.
+ *
+ * @param trans The transformation to apply.
+ */
+ void Transform(const Geom::Affine &trans);
+
+ // decompose le chemin en ses sous-chemin
+ // killNoSurf=true -> oublie les chemins de surface nulle
+ Path** SubPaths(int &outNb,bool killNoSurf);
+ // pour recuperer les trous
+ // nbNest= nombre de contours
+ // conts= debut de chaque contour
+ // nesting= parent de chaque contour
+ Path** SubPathsWithNesting(int &outNb,bool killNoSurf,int nbNest,int* nesting,int* conts);
+ // surface du chemin (considere comme ferme)
+ double Surface();
+ void PolylineBoundingBox(double &l,double &t,double &r,double &b);
+ void FastBBox(double &l,double &t,double &r,double &b);
+ // longueur (totale des sous-chemins)
+ double Length();
+
+ void ConvertForcedToMoveTo();
+ void ConvertForcedToVoid();
+ struct cut_position {
+ int piece;
+ double t;
+ };
+ cut_position* CurvilignToPosition(int nbCv,double* cvAbs,int &nbCut);
+ cut_position PointToCurvilignPosition(Geom::Point const &pos, unsigned seg = 0) const;
+ //Should this take a cut_position as a param?
+ double PositionToLength(int piece, double t);
+
+ // caution: not tested on quadratic b-splines, most certainly buggy
+ void ConvertPositionsToMoveTo(int nbPos,cut_position* poss);
+ void ConvertPositionsToForced(int nbPos,cut_position* poss);
+
+ void Affiche();
+ char *svg_dump_path() const;
+
+ bool IsLineSegment(int piece);
+
+ private:
+ // utilitary functions for the path construction
+ void CancelBezier ();
+ void CloseSubpath();
+ void InsertMoveTo (Geom::Point const &iPt,int at);
+ void InsertForcePoint (int at);
+ void InsertLineTo (Geom::Point const &iPt,int at);
+ void InsertArcTo (Geom::Point const &ip, double iRx, double iRy, double angle, bool iLargeArc, bool iClockwise,int at);
+ void InsertCubicTo (Geom::Point const &ip, Geom::Point const &iStD, Geom::Point const &iEnD,int at);
+ void InsertBezierTo (Geom::Point const &iPt,int iNb,int at);
+ void InsertIntermBezierTo (Geom::Point const &iPt,int at);
+
+ // creation of dashes: take the polyline given by spP (length spL) and dash it according to head, body, etc. put the result in
+ // the polyline of this instance
+ void DashSubPath(int spL, int spP, std::vector<path_lineto> const &orig_pts, float head,float tail,float body,int nbD, const float dashs[],bool stPlain,float stOffset);
+
+ // Functions used by the conversion.
+ // they append points to the polyline
+ /**
+ * The function is quite similar to RecCubicTo. Some of the maths, specially that in
+ * ArcAnglesAndCenter is too cryptic and I have not spent enough time deriving it yet either. The
+ * important thing is how the Arc is split into line segments and that I can explain. Given the
+ * threshold and the two radii, a maximum angle is calculated. This angle is a measure of how big
+ * a sub-arc you can substitute with a line segment without breaking the threshold. Then you
+ * divide the whole arc into sectors such that each one's angle is under or equal to maximum
+ * angle.
+ *
+ * @image html livarot-images/arc-threshold.svg
+ *
+ * In this image, the red dashed arc is the actual arc that was to be approximated. The blue arcs
+ * are sectors, each one having an angle equal to or smaller than maximum angle (which is 20
+ * degrees) in this example. The final polyline approximation is shown by the pink dotted line
+ * segments.
+ *
+ * TODO: Understand the maths in ArcAnglesAndCenter and how the maximum angle is calculated.
+ *
+ */
+ void DoArc ( Geom::Point const &iS, Geom::Point const &iE, double rx, double ry,
+ double angle, bool large, bool wise, double tresh);
+ /**
+ * Approximate the passed cubic bezier with line segments.
+ *
+ * Basically the function checks if the passed cubic Bezier is "small enough" and if
+ * it is, it does nothing, if it however isn't "small enough", it splits the cubic Bezier
+ * curve into two cubic Bezier curves (split at mid point), recursively calls itself on the
+ * left cubic, add the midpoint to the polyline approximation, call itself on the right
+ * cubic and done. lev is the maximum recursion depth possible, once it's reached, the function
+ * returns doing nothing immediately. See the code to understand more about maxL.
+ *
+ * The way the algorithm checks if the curve is "small enough" is maths so I'll try to
+ * explain it here so you can see the equations printed and probably refer it when reading code.
+ *
+ * Let \f$\vec{p_{0}}\f$, \f$\vec{p_{1}}\f$, \f$\vec{p_{2}}\f$ and \f$\vec{p_{3}}\f$ be the four
+ * points that define a cubic Bezier. The first is the start point, last is the end point,
+ * the two in between are the control points. Given this let me relate these points to the
+ * arguments that were passed in.
+ *
+ * \f[ \vec{iS} = \vec{p_{0}}\f]
+ * \f[ \vec{iE} = \vec{p_{3}}\f]
+ * \f[ \vec{iSd} = 3 (\vec{p_{1}} - \vec{p_{0}})\f]
+ * \f[ \vec{iEd} = 3 (\vec{p_{3}} - \vec{p_{2}})\f]
+ *
+ * This is just how livarot represents a Cubic Bezier, nothing I can do about that. The code
+ * starts by calculating a vector from start point to end point.
+ *
+ * \f[ \vec{se} = \vec{iE} - \vec{iS} ]\f
+ *
+ * If the length of \f$\vec{se}\f$ is smaller than 0.01, then the cubic bezier's endpoints are
+ * kinda close, but if the control points are too far away, it can still be a long tall curve,
+ * so let's see the control points and see how far away they are from the \f$\vec{se}\f$ vector.
+ * To do that, we measure the lengths of \f$\vec{iSd}\f$ and \f$\vec{iEd}\f$. If both are below
+ * threshold, we return immediately since it indicates the cubic bezier is "small enough".
+ *
+ * if the length is greater than 0.01, we still check the y projections of the control handles
+ * on the line between start and end points, if these projections are limited by the threshold
+ * and we didn't mess up the maxL restriction, we are good.
+ *
+ * If we ran out of recursion levels, we return anyways. In case this cubic bezier isn't small
+ * enough, we split it in two parts. There are math equations in the code that do this and I
+ * spent hours deriving it and they are totally correct. Basically take the usual maths to split
+ * a cubic Bezier into two parts and just account for the factor of 3 in the control handles
+ * that livarot adds and you'll end up with correct equations.
+ *
+ * TODO: Add derivation here maybe?
+ *
+ */
+ void RecCubicTo ( Geom::Point const &iS, Geom::Point const &iSd, Geom::Point const &iE, Geom::Point const &iEd, double tresh, int lev,
+ double maxL = -1.0);
+ void RecBezierTo ( Geom::Point const &iPt, Geom::Point const &iS, Geom::Point const &iE, double treshhold, int lev, double maxL = -1.0);
+
+ void DoArc ( Geom::Point const &iS, Geom::Point const &iE, double rx, double ry,
+ double angle, bool large, bool wise, double tresh, int piece);
+ void RecCubicTo ( Geom::Point const &iS, Geom::Point const &iSd, Geom::Point const &iE, Geom::Point const &iEd, double tresh, int lev,
+ double st, double et, int piece);
+ void RecBezierTo ( Geom::Point const &iPt, Geom::Point const &iS, const Geom::Point &iE, double treshhold, int lev, double st, double et,
+ int piece);
+
+ // don't pay attention
+ struct offset_orig
+ {
+ Path *orig;
+ int piece;
+ double tSt, tEn;
+ double off_dec;
+ };
+ void DoArc ( Geom::Point const &iS, Geom::Point const &iE, double rx, double ry,
+ double angle, bool large, bool wise, double tresh, int piece,
+ offset_orig & orig);
+ void RecCubicTo ( Geom::Point const &iS, Geom::Point const &iSd, Geom::Point const &iE, Geom::Point const &iEd, double tresh, int lev,
+ double st, double et, int piece, offset_orig & orig);
+ void RecBezierTo ( Geom::Point const &iPt, Geom::Point const &iS, Geom::Point const &iE, double treshhold, int lev, double st, double et,
+ int piece, offset_orig & orig);
+
+ static void ArcAngles ( Geom::Point const &iS, Geom::Point const &iE, double rx,
+ double ry, double angle, bool large, bool wise,
+ double &sang, double &eang);
+ static void QuadraticPoint (double t, Geom::Point &oPt, Geom::Point const &iS, Geom::Point const &iM, Geom::Point const &iE);
+ static void CubicTangent (double t, Geom::Point &oPt, Geom::Point const &iS,
+ Geom::Point const &iSd, Geom::Point const &iE,
+ Geom::Point const &iEd);
+
+ struct outline_callback_data
+ {
+ Path *orig;
+ int piece;
+ double tSt, tEn;
+ Path *dest;
+ double x1, y1, x2, y2;
+ union
+ {
+ struct
+ {
+ double dx1, dy1, dx2, dy2;
+ }
+ c;
+ struct
+ {
+ double mx, my;
+ }
+ b;
+ struct
+ {
+ double rx, ry, angle;
+ bool clock, large;
+ double stA, enA;
+ }
+ a;
+ }
+ d;
+ };
+
+ typedef void (outlineCallback) (outline_callback_data * data, double tol, double width);
+ struct outline_callbacks
+ {
+ outlineCallback *cubicto;
+ outlineCallback *bezierto;
+ outlineCallback *arcto;
+ };
+
+ void SubContractOutline (int off, int num_pd,
+ Path * dest, outline_callbacks & calls,
+ double tolerance, double width, JoinType join,
+ ButtType butt, double miter, bool closeIfNeeded,
+ bool skipMoveto, Geom::Point & lastP, Geom::Point & lastT);
+ void DoStroke(int off, int N, Shape *dest, bool doClose, double width, JoinType join,
+ ButtType butt, double miter, bool justAdd = false);
+
+ static void TangentOnSegAt(double at, Geom::Point const &iS, PathDescrLineTo const &fin,
+ Geom::Point &pos, Geom::Point &tgt, double &len);
+ static void TangentOnArcAt(double at, Geom::Point const &iS, PathDescrArcTo const &fin,
+ Geom::Point &pos, Geom::Point &tgt, double &len, double &rad);
+ static void TangentOnCubAt (double at, Geom::Point const &iS, PathDescrCubicTo const &fin, bool before,
+ Geom::Point &pos, Geom::Point &tgt, double &len, double &rad);
+ static void TangentOnBezAt (double at, Geom::Point const &iS,
+ PathDescrIntermBezierTo & mid,
+ PathDescrBezierTo & fin, bool before,
+ Geom::Point & pos, Geom::Point & tgt, double &len, double &rad);
+ static void OutlineJoin (Path * dest, Geom::Point pos, Geom::Point stNor, Geom::Point enNor,
+ double width, JoinType join, double miter, int nType);
+
+ static bool IsNulCurve (std::vector<PathDescr*> const &cmd, int curD, Geom::Point const &curX);
+
+ static void RecStdCubicTo (outline_callback_data * data, double tol,
+ double width, int lev);
+ static void StdCubicTo (outline_callback_data * data, double tol,
+ double width);
+ static void StdBezierTo (outline_callback_data * data, double tol,
+ double width);
+ static void RecStdArcTo (outline_callback_data * data, double tol,
+ double width, int lev);
+ static void StdArcTo (outline_callback_data * data, double tol, double width);
+
+
+ // fonctions annexes pour le stroke
+ static void DoButt (Shape * dest, double width, ButtType butt, Geom::Point pos,
+ Geom::Point dir, int &leftNo, int &rightNo);
+ static void DoJoin (Shape * dest, double width, JoinType join, Geom::Point pos,
+ Geom::Point prev, Geom::Point next, double miter, double prevL,
+ double nextL, int *stNo, int *enNo);
+ static void DoLeftJoin (Shape * dest, double width, JoinType join, Geom::Point pos,
+ Geom::Point prev, Geom::Point next, double miter, double prevL,
+ double nextL, int &leftStNo, int &leftEnNo,int pathID=-1,int pieceID=0,double tID=0.0);
+ static void DoRightJoin (Shape * dest, double width, JoinType join, Geom::Point pos,
+ Geom::Point prev, Geom::Point next, double miter, double prevL,
+ double nextL, int &rightStNo, int &rightEnNo,int pathID=-1,int pieceID=0,double tID=0.0);
+ static void RecRound (Shape * dest, int sNo, int eNo,
+ Geom::Point const &iS, Geom::Point const &iE,
+ Geom::Point const &nS, Geom::Point const &nE,
+ Geom::Point &origine,float width);
+
+
+ /**
+ * Simpilfy a sequence of points.
+ *
+ * Fit cubic Bezier patches on the sequence of points defined by the passed parameters. This
+ * sequence is just a subset of the polyline approximation points stored in the Path object.
+ *
+ * @param off The offset to the first point to process.
+ * @param N The total number of points in the sequence.
+ * @param threshhold The threshold to respect during simplification. The higher this number is,
+ * the more relaxed you're making the simplifier. The smaller the number, the more strict you're
+ * making the simplifier.
+ */
+ void DoSimplify(int off, int N, double treshhold);
+
+ /**
+ * Fit a cubic Bezier patch on the sequence of points.
+ *
+ * @param off The index of the first point of the sequence to fit on.
+ * @param N The total number of points you want to fit on.
+ * @param res Reference to the Cubic Bezier description where the resulting control points will
+ * be stored.
+ * @param worstP Reference to a point index. This will be changed to whichever point measures the
+ * highest deviation from the fitted curve.
+ *
+ * @return True if the fit respected threshold, false otherwise.
+ */
+ bool AttemptSimplify(int off, int N, double treshhold, PathDescrCubicTo &res, int &worstP);
+ /*
+ * The actual fitting code that takes a sequence and fits stuff on it.
+ *
+ * Totally based on the algorithm from:
+ * http://www.cs.mtu.edu/~shene/COURSES/cs3621/NOTES/INT-APP/CURVE-APP-global.html
+ *
+ * @param start The start point of the cubic bezier which is already known.
+ * @param res The cubic Bezier path command that function will populate after doing the maths.
+ * @param Xk The array of X coordinates of the point to fit.
+ * @param Yk The array of Y coordinates of the point to fit.
+ * @param Qk An array to store some intermediate values.
+ * @param tk The time values for the points.
+ * @param nbPt The total points to fit on.
+ *
+ * @return True if the fit was done correctly, false if something bad happened. (Non-invertible
+ * matrix).
+ */
+ static bool FitCubic(Geom::Point const &start,
+ PathDescrCubicTo &res,
+ double *Xk, double *Yk, double *Qk, double *tk, int nbPt);
+ /**
+ * Structure to keep some data for fitting.
+ *
+ * Note that the pointers in the structure are going to store arrays. The comments explain what
+ * each element of a particular array stores. Also note that the length mentioned in the comment
+ * for tk and lk is not the straight line distance but the length as measured by walking on the
+ * line segments connecting the points.
+ */
+ struct fitting_tables {
+ int nbPt; /*!< The points to fit on in a particular iteration */
+ int maxPt; /*!< Maximum number of points these arrays here can store */
+ int inPt; /*!< Total points whose X, Y, lk are all populated here */
+ double *Xk; /*!< X coordinate of the point */
+ double *Yk; /*!< Y coordinate of the point */
+ double *Qk; /*!< A special value needed by the fitting algorithm */
+ double *tk; /*!< A number between 0 and 1 that is the fraction (length b/w first point to this point along the line segments)/(total length) */
+ double *lk; /*!< Length of the line segment from the previous point to this point */
+ char *fk; /*!< A flag if 0x01 indicates forced point and if 0x00 indicates a normal point */
+ double totLen; /*!< Total length of the polyline or you can say the sum of lengths of all line segments. */
+ };
+
+ /**
+ * Fit Cubic Bezier patch using the fitting table data.
+ *
+ * @param data The fitting_tables data needed for fitting. ExtendFit sets that up for this
+ * function.
+ * @param threshhold The threshold to respect.
+ * @param res The cubic Bezier command which this function will populate.
+ * @param worstP The point with the worst error.
+ */
+ bool AttemptSimplify (fitting_tables &data,double treshhold, PathDescrCubicTo & res,int &worstP);
+
+ /**
+ * Fit Cubic Bezier patch on the points.
+ *
+ * This uses data already calculated by probably the same function if it exists.
+ * The data that's reused is apparently the X, Y and lk values. However, I think there is a
+ * problem with this caching mechanism. See the inline comments of ExtendFit.
+ *
+ * This function prepares data in fitting tables and calls the AttemptSimplify version that takes
+ * fitting_tables data.
+ *
+ * @param off The offset to the first point.
+ * @param N The total number of points in that sequence.
+ * @param data The data structure which keeps data saved for later use by the same function.
+ * @param threshhold The threshold to respect.
+ * @param res cubic Bezier path command where this function will store the control point handles.
+ * @param worstP Function will set this to the point with the worst error.
+ *
+ * @return True if the threshold was respected, otherwise false.
+ */
+ bool ExtendFit(int off, int N, fitting_tables &data,double treshhold, PathDescrCubicTo & res,int &worstP);
+ /**
+ * Peform an iteration of Newton-Raphson to improve t values.
+ *
+ * TODO: Place derivation here with embedded latex maybe.
+ */
+ double RaffineTk (Geom::Point pt, Geom::Point p0, Geom::Point p1, Geom::Point p2, Geom::Point p3, double it);
+ void FlushPendingAddition(Path* dest,PathDescr *lastAddition,PathDescrCubicTo &lastCubic,int lastAD);
+
+private:
+ /**
+ * Add a Geom::Curve's equivalent path description.
+ *
+ * Any straight curve (line or otherwise that's straight) is added as line. CubicBezier
+ * and EllipticalArcs are handled manually, while any other Geom::Curve type is handled by
+ * converting to cubic beziers using Geom::cubicbezierpath_from_sbasis and recursively calling
+ * the same function.
+ *
+ * There is one special reason for using is_straight_curve to figure out if a CubicBezier is
+ * actually a line and making sure that it is added as a line not as a straight line CubicBezier
+ * (a CubicBezier with control points being the same as end points). Sometimes when you're drawing
+ * straight line segments with the Bezier (pen) tool, Inkscape would place a straight CubicBezier
+ * instead of a line segment. The call to Path::Convert or Path::ConvertWithBackData would break
+ * up this line segment into smaller line segments which is not what we want (we want it to break
+ * only real curves) not curves that are actually just straight lines.
+ *
+ * @param c The Geom::Curve whose path description to create/add.
+ */
+ void AddCurve(Geom::Curve const &c);
+
+};
+#endif
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/livarot/PathConversion.cpp b/src/livarot/PathConversion.cpp
new file mode 100644
index 0000000..81dc32d
--- /dev/null
+++ b/src/livarot/PathConversion.cpp
@@ -0,0 +1,1638 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * TODO: insert short description here
+ *//*
+ * Authors:
+ * see git history
+ * Fred
+ *
+ * Copyright (C) 2018 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <glib.h>
+#include <2geom/transforms.h>
+#include "Path.h"
+#include "Shape.h"
+#include "livarot/path-description.h"
+
+/*
+ * path description -> polyline
+ * and Path -> Shape (the Fill() function at the bottom)
+ * nathing fancy here: take each command and append an approximation of it to the polyline
+ */
+
+void Path::ConvertWithBackData(double treshhold)
+{
+ // if a quadratic Bezier spline was being added (Path::BezierTo or Path::TempBezierTo
+ // were called and Path::EndBezierTo hasn't been called yet), cancel it
+ if ( descr_flags & descr_adding_bezier ) {
+ CancelBezier();
+ }
+
+ // are we doing a sub path? if yes, clear the flags. CloseSubPath just clears the flags
+ // it doesn't close a sub path
+ if ( descr_flags & descr_doing_subpath ) {
+ CloseSubpath();
+ }
+
+ // set the backdata flag to true since this function will be calculating and storing backdata stuff
+ SetBackData(true);
+ // clears any pre-existing polyline approximation stuff
+ ResetPoints();
+
+ // nothing to approximate so return
+ if ( descr_cmd.empty() ) {
+ return;
+ }
+
+ Geom::Point curX; // the last point added
+ // the description to process. We start with 1 usually since the first is always a MoveTo that
+ // we handle before the loop (see below). In the case that the first is not a moveTo, We set
+ // curP to 0.
+ int curP = 1;
+ // index of the last moveto point. Useful when a close path command is encountered.
+ int lastMoveTo = -1;
+
+ // The initial moveto.
+ // if the first command is a moveTo, set that as the lastPoint (curX) otherwise add a point at
+ // the origin as a moveTo.
+ {
+ int const firstTyp = descr_cmd[0]->getType();
+ if ( firstTyp == descr_moveto ) {
+ curX = dynamic_cast<PathDescrMoveTo *>(descr_cmd[0])->p;
+ } else {
+ curP = 0;
+ curX[Geom::X] = curX[Geom::Y] = 0;
+ }
+ // tiny detail to see here is that piece (the index of the path command this point comes from) is set to 0 which
+ // may or may not be true. If there was not a MoveTo, index 0 can have other description types.
+ lastMoveTo = AddPoint(curX, 0, 0.0, true);
+ }
+
+ // And the rest, one by one.
+ // within this loop, curP holds the current path command index, curX holds the last point added
+ while ( curP < int(descr_cmd.size()) ) {
+
+ int const nType = descr_cmd[curP]->getType();
+ Geom::Point nextX;
+
+ switch (nType) {
+ case descr_forced: {
+ // just add a forced point (at the last point added). These arguments are useless by the way.
+ AddForcedPoint(curX, curP, 1.0);
+ curP++;
+ break;
+ }
+
+ case descr_moveto: {
+ PathDescrMoveTo *nData = dynamic_cast<PathDescrMoveTo*>(descr_cmd[curP]);
+ nextX = nData->p;
+ // add the moveTo point and also store this in lastMoveTo
+ lastMoveTo = AddPoint(nextX, curP, 0.0, true);
+ // et on avance
+ curP++;
+ break;
+ }
+
+ case descr_close: {
+ // add the lastMoveTo point again
+ nextX = pts[lastMoveTo].p;
+ int n = AddPoint(nextX, curP, 1.0, false);
+ // we check if n > 0 because in some cases the last point has already been added so AddPoint would
+ // return -1 .. but then that last point won't get marked with closed = true .. I wonder if that would cause
+ // problems. Just to explain this say:
+ // MoveTo(0, 0); LineTo(10, 0); LineTo(10, 10); LineTo(0, 10); LineTo(0, 0); Close();
+ // LineTo(0, 0) would have already added a point at origin. So AddPoint won't add anything. But my point is that
+ // then the last point (0,0) won't get marked as closed = true which it should be.
+ // But then maybe this doesn't matter because closed variable is barely used.
+ if (n > 0) pts[n].closed = true;
+ curP++;
+ break;
+ }
+
+ case descr_lineto: {
+ PathDescrLineTo *nData = dynamic_cast<PathDescrLineTo *>(descr_cmd[curP]);
+ nextX = nData->p;
+ AddPoint(nextX,curP,1.0,false);
+ // et on avance
+ curP++;
+ break;
+ }
+
+ case descr_cubicto: {
+ PathDescrCubicTo *nData = dynamic_cast<PathDescrCubicTo *>(descr_cmd[curP]);
+ nextX = nData->p;
+ // RecCubicTo will see if threshold is fine with approximating this cubic bezier with
+ // a line segment through the start and end points. If no, it'd split the cubic at its
+ // center point and recursively call itself on the left and right side. The center point
+ // gets added in the points list too.
+ RecCubicTo(curX, nData->start, nextX, nData->end, treshhold, 8, 0.0, 1.0, curP);
+ // RecCubicTo adds any points inside the cubic and last one is added here
+ AddPoint(nextX, curP, 1.0, false);
+ // et on avance
+ curP++;
+ break;
+ }
+
+ case descr_arcto: {
+ PathDescrArcTo *nData = dynamic_cast<PathDescrArcTo *>(descr_cmd[curP]);
+ nextX = nData->p;
+ // Similar to RecCubicTo, just for Arcs
+ DoArc(curX, nextX, nData->rx, nData->ry, nData->angle, nData->large, nData->clockwise, treshhold, curP);
+ AddPoint(nextX, curP, 1.0, false);
+ // et on avance
+ curP++;
+ break;
+ }
+
+ case descr_bezierto: {
+ PathDescrBezierTo *nBData = dynamic_cast<PathDescrBezierTo *>(descr_cmd[curP]);
+ int nbInterm = nBData->nb;
+ nextX = nBData->p;
+ // same as RecCubicTo and RecArcTo but for quadratic bezier splines
+
+ int ip = curP + 1;
+ PathDescrIntermBezierTo *nData = dynamic_cast<PathDescrIntermBezierTo *>(descr_cmd[ip]);
+
+ if ( nbInterm >= 1 ) {
+ Geom::Point bx = curX;
+ Geom::Point dx = nData->p;
+ Geom::Point cx = 2 * bx - dx;
+
+ ip++;
+ nData = dynamic_cast<PathDescrIntermBezierTo *>(descr_cmd[ip]);
+
+ for (int k = 0; k < nbInterm - 1; k++) {
+ bx = cx;
+ cx = dx;
+
+ dx = nData->p;
+ ip++;
+ nData = dynamic_cast<PathDescrIntermBezierTo *>(descr_cmd[ip]);
+
+ Geom::Point stx;
+ stx = (bx + cx) / 2;
+ if ( k > 0 ) {
+ AddPoint(stx,curP - 1+k,1.0,false);
+ }
+
+ {
+ Geom::Point mx;
+ mx = (cx + dx) / 2;
+ RecBezierTo(cx, stx, mx, treshhold, 8, 0.0, 1.0, curP + k);
+ }
+ }
+ {
+ bx = cx;
+ cx = dx;
+
+ dx = nextX;
+ dx = 2 * dx - cx;
+
+ Geom::Point stx;
+ stx = (bx + cx) / 2;
+
+ if ( nbInterm > 1 ) {
+ AddPoint(stx, curP + nbInterm - 2, 1.0, false);
+ }
+
+ {
+ Geom::Point mx;
+ mx = (cx + dx) / 2;
+ RecBezierTo(cx, stx, mx, treshhold, 8, 0.0, 1.0, curP + nbInterm - 1);
+ }
+ }
+
+ }
+
+
+ AddPoint(nextX, curP - 1 + nbInterm, 1.0, false);
+
+ // et on avance
+ curP += 1 + nbInterm;
+ break;
+ }
+ }
+ curX = nextX;
+ }
+}
+
+
+void Path::Convert(double treshhold)
+{
+ if ( descr_flags & descr_adding_bezier ) {
+ CancelBezier();
+ }
+
+ if ( descr_flags & descr_doing_subpath ) {
+ CloseSubpath();
+ }
+
+ SetBackData(false);
+ ResetPoints();
+ if ( descr_cmd.empty() ) {
+ return;
+ }
+
+ Geom::Point curX;
+ int curP = 1;
+ int lastMoveTo = 0;
+
+ // le moveto
+ {
+ int const firstTyp = descr_cmd[0]->getType();
+ if ( firstTyp == descr_moveto ) {
+ curX = dynamic_cast<PathDescrMoveTo *>(descr_cmd[0])->p;
+ } else {
+ curP = 0;
+ curX[0] = curX[1] = 0;
+ }
+ lastMoveTo = AddPoint(curX, true);
+ }
+ descr_cmd[0]->associated = lastMoveTo;
+
+ // et le reste, 1 par 1
+ while ( curP < int(descr_cmd.size()) ) {
+
+ int const nType = descr_cmd[curP]->getType();
+ Geom::Point nextX;
+
+ switch (nType) {
+ case descr_forced: {
+ descr_cmd[curP]->associated = AddForcedPoint(curX);
+ curP++;
+ break;
+ }
+
+ case descr_moveto: {
+ PathDescrMoveTo *nData = dynamic_cast<PathDescrMoveTo *>(descr_cmd[curP]);
+ nextX = nData->p;
+ lastMoveTo = AddPoint(nextX, true);
+ descr_cmd[curP]->associated = lastMoveTo;
+
+ // et on avance
+ curP++;
+ break;
+ }
+
+ case descr_close: {
+ nextX = pts[lastMoveTo].p;
+ descr_cmd[curP]->associated = AddPoint(nextX, false);
+ if ( descr_cmd[curP]->associated < 0 ) {
+ if ( curP == 0 ) {
+ descr_cmd[curP]->associated = 0;
+ } else {
+ descr_cmd[curP]->associated = descr_cmd[curP - 1]->associated;
+ }
+ }
+ if ( descr_cmd[curP]->associated > 0 ) {
+ pts[descr_cmd[curP]->associated].closed = true;
+ }
+ curP++;
+ break;
+ }
+
+ case descr_lineto: {
+ PathDescrLineTo *nData = dynamic_cast<PathDescrLineTo *>(descr_cmd[curP]);
+ nextX = nData->p;
+ descr_cmd[curP]->associated = AddPoint(nextX, false);
+ if ( descr_cmd[curP]->associated < 0 ) {
+ if ( curP == 0 ) {
+ descr_cmd[curP]->associated = 0;
+ } else {
+ descr_cmd[curP]->associated = descr_cmd[curP - 1]->associated;
+ }
+ }
+ // et on avance
+ curP++;
+ break;
+ }
+
+ case descr_cubicto: {
+ PathDescrCubicTo *nData = dynamic_cast<PathDescrCubicTo *>(descr_cmd[curP]);
+ nextX = nData->p;
+ RecCubicTo(curX, nData->start, nextX, nData->end, treshhold, 8);
+ descr_cmd[curP]->associated = AddPoint(nextX,false);
+ if ( descr_cmd[curP]->associated < 0 ) {
+ if ( curP == 0 ) {
+ descr_cmd[curP]->associated = 0;
+ } else {
+ descr_cmd[curP]->associated = descr_cmd[curP - 1]->associated;
+ }
+ }
+ // et on avance
+ curP++;
+ break;
+ }
+
+ case descr_arcto: {
+ PathDescrArcTo *nData = dynamic_cast<PathDescrArcTo *>(descr_cmd[curP]);
+ nextX = nData->p;
+ DoArc(curX, nextX, nData->rx, nData->ry, nData->angle, nData->large, nData->clockwise, treshhold);
+ descr_cmd[curP]->associated = AddPoint(nextX, false);
+ if ( descr_cmd[curP]->associated < 0 ) {
+ if ( curP == 0 ) {
+ descr_cmd[curP]->associated = 0;
+ } else {
+ descr_cmd[curP]->associated = descr_cmd[curP - 1]->associated;
+ }
+ }
+ // et on avance
+ curP++;
+ break;
+ }
+
+ case descr_bezierto: {
+ PathDescrBezierTo *nBData = dynamic_cast<PathDescrBezierTo *>(descr_cmd[curP]);
+ int nbInterm = nBData->nb;
+ nextX = nBData->p;
+ int curBD = curP;
+
+ curP++;
+ int ip = curP;
+ PathDescrIntermBezierTo *nData = dynamic_cast<PathDescrIntermBezierTo *>(descr_cmd[ip]);
+
+ if ( nbInterm == 1 ) {
+ Geom::Point const midX = nData->p;
+ RecBezierTo(midX, curX, nextX, treshhold, 8);
+ } else if ( nbInterm > 1 ) {
+ Geom::Point bx = curX;
+ Geom::Point dx = nData->p;
+ Geom::Point cx = 2 * bx - dx;
+
+ ip++;
+ nData = dynamic_cast<PathDescrIntermBezierTo *>(descr_cmd[ip]);
+
+ for (int k = 0; k < nbInterm - 1; k++) {
+ bx = cx;
+ cx = dx;
+
+ dx = nData->p;
+ ip++;
+ nData = dynamic_cast<PathDescrIntermBezierTo *>(descr_cmd[ip]);
+
+ Geom::Point stx = (bx + cx) / 2;
+ if ( k > 0 ) {
+ descr_cmd[ip - 2]->associated = AddPoint(stx, false);
+ if ( descr_cmd[ip - 2]->associated < 0 ) {
+ if ( curP == 0 ) {
+ descr_cmd[ip - 2]->associated = 0;
+ } else {
+ descr_cmd[ip - 2]->associated = descr_cmd[ip - 3]->associated;
+ }
+ }
+ }
+
+ {
+ Geom::Point const mx = (cx + dx) / 2;
+ RecBezierTo(cx, stx, mx, treshhold, 8);
+ }
+ }
+
+ {
+ bx = cx;
+ cx = dx;
+
+ dx = nextX;
+ dx = 2 * dx - cx;
+
+ Geom::Point stx = (bx + cx) / 2;
+
+ descr_cmd[ip - 1]->associated = AddPoint(stx, false);
+ if ( descr_cmd[ip - 1]->associated < 0 ) {
+ if ( curP == 0 ) {
+ descr_cmd[ip - 1]->associated = 0;
+ } else {
+ descr_cmd[ip - 1]->associated = descr_cmd[ip - 2]->associated;
+ }
+ }
+
+ {
+ Geom::Point mx = (cx + dx) / 2;
+ RecBezierTo(cx, stx, mx, treshhold, 8);
+ }
+ }
+ }
+
+ descr_cmd[curBD]->associated = AddPoint(nextX, false);
+ if ( descr_cmd[curBD]->associated < 0 ) {
+ if ( curP == 0 ) {
+ descr_cmd[curBD]->associated = 0;
+ } else {
+ descr_cmd[curBD]->associated = descr_cmd[curBD - 1]->associated;
+ }
+ }
+
+ // et on avance
+ curP += nbInterm;
+ break;
+ }
+ }
+
+ curX = nextX;
+ }
+}
+
+void Path::ConvertEvenLines(double treshhold)
+{
+ if ( descr_flags & descr_adding_bezier ) {
+ CancelBezier();
+ }
+
+ if ( descr_flags & descr_doing_subpath ) {
+ CloseSubpath();
+ }
+
+ SetBackData(false);
+ ResetPoints();
+ if ( descr_cmd.empty() ) {
+ return;
+ }
+
+ Geom::Point curX;
+ int curP = 1;
+ int lastMoveTo = 0;
+
+ // le moveto
+ {
+ int const firstTyp = descr_cmd[0]->getType();
+ if ( firstTyp == descr_moveto ) {
+ curX = dynamic_cast<PathDescrMoveTo *>(descr_cmd[0])->p;
+ } else {
+ curP = 0;
+ curX[0] = curX[1] = 0;
+ }
+ lastMoveTo = AddPoint(curX, true);
+ }
+ descr_cmd[0]->associated = lastMoveTo;
+
+ // et le reste, 1 par 1
+ while ( curP < int(descr_cmd.size()) ) {
+
+ int const nType = descr_cmd[curP]->getType();
+ Geom::Point nextX;
+
+ switch (nType) {
+ case descr_forced: {
+ descr_cmd[curP]->associated = AddForcedPoint(curX);
+ curP++;
+ break;
+ }
+
+ case descr_moveto: {
+ PathDescrMoveTo* nData = dynamic_cast<PathDescrMoveTo *>(descr_cmd[curP]);
+ nextX = nData->p;
+ lastMoveTo = AddPoint(nextX,true);
+ descr_cmd[curP]->associated = lastMoveTo;
+
+ // et on avance
+ curP++;
+ break;
+ }
+
+ case descr_close: {
+ nextX = pts[lastMoveTo].p;
+ {
+ Geom::Point nexcur;
+ nexcur = nextX - curX;
+ const double segL = Geom::L2(nexcur);
+ if ( (segL > treshhold) && (treshhold > 0) ) {
+ for (double i = treshhold; i < segL; i += treshhold) {
+ Geom::Point nX;
+ nX = (segL - i) * curX + i * nextX;
+ nX /= segL;
+ AddPoint(nX);
+ }
+ }
+ }
+
+ descr_cmd[curP]->associated = AddPoint(nextX,false);
+ if ( descr_cmd[curP]->associated < 0 ) {
+ if ( curP == 0 ) {
+ descr_cmd[curP]->associated = 0;
+ } else {
+ descr_cmd[curP]->associated = descr_cmd[curP - 1]->associated;
+ }
+ }
+ if ( descr_cmd[curP]->associated > 0 ) {
+ pts[descr_cmd[curP]->associated].closed = true;
+ }
+ curP++;
+ break;
+ }
+
+ case descr_lineto: {
+ PathDescrLineTo* nData = dynamic_cast<PathDescrLineTo *>(descr_cmd[curP]);
+ nextX = nData->p;
+ Geom::Point nexcur = nextX - curX;
+ const double segL = L2(nexcur);
+ if ( (segL > treshhold) && (treshhold > 0)) {
+ for (double i = treshhold; i < segL; i += treshhold) {
+ Geom::Point nX = ((segL - i) * curX + i * nextX) / segL;
+ AddPoint(nX);
+ }
+ }
+
+ descr_cmd[curP]->associated = AddPoint(nextX,false);
+ if ( descr_cmd[curP]->associated < 0 ) {
+ if ( curP == 0 ) {
+ descr_cmd[curP]->associated = 0;
+ } else {
+ descr_cmd[curP]->associated = descr_cmd[curP - 1]->associated;
+ }
+ }
+ // et on avance
+ curP++;
+ break;
+ }
+
+ case descr_cubicto: {
+ PathDescrCubicTo *nData = dynamic_cast<PathDescrCubicTo *>(descr_cmd[curP]);
+ nextX = nData->p;
+ RecCubicTo(curX, nData->start, nextX, nData->end, treshhold, 8, 4 * treshhold);
+ descr_cmd[curP]->associated = AddPoint(nextX, false);
+ if ( descr_cmd[curP]->associated < 0 ) {
+ if ( curP == 0 ) {
+ descr_cmd[curP]->associated = 0;
+ } else {
+ descr_cmd[curP]->associated = descr_cmd[curP - 1]->associated;
+ }
+ }
+ // et on avance
+ curP++;
+ break;
+ }
+
+ case descr_arcto: {
+ PathDescrArcTo *nData = dynamic_cast<PathDescrArcTo *>(descr_cmd[curP]);
+ nextX = nData->p;
+ DoArc(curX, nextX, nData->rx, nData->ry, nData->angle, nData->large, nData->clockwise, treshhold);
+ descr_cmd[curP]->associated =AddPoint(nextX, false);
+ if ( descr_cmd[curP]->associated < 0 ) {
+ if ( curP == 0 ) {
+ descr_cmd[curP]->associated = 0;
+ } else {
+ descr_cmd[curP]->associated = descr_cmd[curP - 1]->associated;
+ }
+ }
+
+ // et on avance
+ curP++;
+ break;
+ }
+
+ case descr_bezierto: {
+ PathDescrBezierTo *nBData = dynamic_cast<PathDescrBezierTo *>(descr_cmd[curP]);
+ int nbInterm = nBData->nb;
+ nextX = nBData->p;
+ int curBD = curP;
+
+ curP++;
+ int ip = curP;
+ PathDescrIntermBezierTo *nData = dynamic_cast<PathDescrIntermBezierTo *>(descr_cmd[ip]);
+
+ if ( nbInterm == 1 ) {
+ Geom::Point const midX = nData->p;
+ RecBezierTo(midX, curX, nextX, treshhold, 8, 4 * treshhold);
+ } else if ( nbInterm > 1 ) {
+ Geom::Point bx = curX;
+ Geom::Point dx = nData->p;
+ Geom::Point cx = 2 * bx - dx;
+
+ ip++;
+ nData = dynamic_cast<PathDescrIntermBezierTo *>(descr_cmd[ip]);
+
+ for (int k = 0; k < nbInterm - 1; k++) {
+ bx = cx;
+ cx = dx;
+ dx = nData->p;
+
+ ip++;
+ nData = dynamic_cast<PathDescrIntermBezierTo *>(descr_cmd[ip]);
+
+ Geom::Point stx = (bx+cx) / 2;
+ if ( k > 0 ) {
+ descr_cmd[ip - 2]->associated = AddPoint(stx, false);
+ if ( descr_cmd[ip - 2]->associated < 0 ) {
+ if ( curP == 0 ) {
+ descr_cmd[ip- 2]->associated = 0;
+ } else {
+ descr_cmd[ip - 2]->associated = descr_cmd[ip - 3]->associated;
+ }
+ }
+ }
+
+ {
+ Geom::Point const mx = (cx + dx) / 2;
+ RecBezierTo(cx, stx, mx, treshhold, 8, 4 * treshhold);
+ }
+ }
+
+ {
+ bx = cx;
+ cx = dx;
+
+ dx = nextX;
+ dx = 2 * dx - cx;
+
+ Geom::Point const stx = (bx + cx) / 2;
+
+ descr_cmd[ip - 1]->associated = AddPoint(stx, false);
+ if ( descr_cmd[ip - 1]->associated < 0 ) {
+ if ( curP == 0 ) {
+ descr_cmd[ip - 1]->associated = 0;
+ } else {
+ descr_cmd[ip - 1]->associated = descr_cmd[ip - 2]->associated;
+ }
+ }
+
+ {
+ Geom::Point const mx = (cx + dx) / 2;
+ RecBezierTo(cx, stx, mx, treshhold, 8, 4 * treshhold);
+ }
+ }
+ }
+
+ descr_cmd[curBD]->associated = AddPoint(nextX, false);
+ if ( descr_cmd[curBD]->associated < 0 ) {
+ if ( curP == 0 ) {
+ descr_cmd[curBD]->associated = 0;
+ } else {
+ descr_cmd[curBD]->associated = descr_cmd[curBD - 1]->associated;
+ }
+ }
+
+ // et on avance
+ curP += nbInterm;
+ break;
+ }
+ }
+ if ( Geom::LInfty(curX - nextX) > 0.00001 ) {
+ curX = nextX;
+ }
+ }
+}
+
+const Geom::Point Path::PrevPoint(int i) const
+{
+ /* TODO: I suspect this should assert `(unsigned) i < descr_nb'. We can probably change
+ the argument to unsigned. descr_nb should probably be changed to unsigned too. */
+ g_assert( i >= 0 );
+ switch ( descr_cmd[i]->getType() ) {
+ case descr_moveto: {
+ PathDescrMoveTo *nData = dynamic_cast<PathDescrMoveTo *>(descr_cmd[i]);
+ return nData->p;
+ }
+ case descr_lineto: {
+ PathDescrLineTo *nData = dynamic_cast<PathDescrLineTo *>(descr_cmd[i]);
+ return nData->p;
+ }
+ case descr_arcto: {
+ PathDescrArcTo *nData = dynamic_cast<PathDescrArcTo *>(descr_cmd[i]);
+ return nData->p;
+ }
+ case descr_cubicto: {
+ PathDescrCubicTo *nData = dynamic_cast<PathDescrCubicTo *>(descr_cmd[i]);
+ return nData->p;
+ }
+ case descr_bezierto: {
+ PathDescrBezierTo *nData = dynamic_cast<PathDescrBezierTo *>(descr_cmd[i]);
+ return nData->p;
+ }
+ case descr_interm_bezier:
+ case descr_close:
+ case descr_forced:
+ return PrevPoint(i - 1);
+ default:
+ g_assert_not_reached();
+ return Geom::Point(0, 0);
+ }
+}
+
+// utilitaries: given a quadratic bezier curve (start point, control point, end point, ie that's a clamped curve),
+// and an abcissis on it, get the point with that abcissis.
+// warning: it's NOT a curvilign abcissis (or whatever you call that in english), so "t" is NOT the length of "start point"->"result point"
+void Path::QuadraticPoint(double t, Geom::Point &oPt,
+ const Geom::Point &iS, const Geom::Point &iM, const Geom::Point &iE)
+{
+ Geom::Point const ax = iE - 2 * iM + iS;
+ Geom::Point const bx = 2 * iM - 2 * iS;
+ Geom::Point const cx = iS;
+
+ oPt = t * t * ax + t * bx + cx;
+}
+// idem for cubic bezier patch
+void Path::CubicTangent(double t, Geom::Point &oPt, const Geom::Point &iS, const Geom::Point &isD,
+ const Geom::Point &iE, const Geom::Point &ieD)
+{
+ Geom::Point const ax = ieD - 2 * iE + 2 * iS + isD;
+ Geom::Point const bx = 3 * iE - ieD - 2 * isD - 3 * iS;
+ Geom::Point const cx = isD;
+
+ oPt = 3 * t * t * ax + 2 * t * bx + cx;
+}
+
+// extract interesting info of a SVG arc description
+static void ArcAnglesAndCenter(Geom::Point const &iS, Geom::Point const &iE,
+ double rx, double ry, double angle,
+ bool large, bool wise,
+ double &sang, double &eang, Geom::Point &dr);
+
+void Path::ArcAngles(const Geom::Point &iS, const Geom::Point &iE,
+ double rx, double ry, double angle, bool large, bool wise, double &sang, double &eang)
+{
+ Geom::Point dr;
+ ArcAnglesAndCenter(iS, iE, rx, ry, angle, large, wise, sang, eang, dr);
+}
+
+/* N.B. If iS == iE then sang,eang,dr each become NaN. Probably a bug. */
+static void ArcAnglesAndCenter(Geom::Point const &iS, Geom::Point const &iE,
+ double rx, double ry, double angle,
+ bool large, bool wise,
+ double &sang, double &eang, Geom::Point &dr)
+{
+ Geom::Point se = iE - iS;
+ Geom::Point ca(cos(angle), sin(angle));
+ Geom::Point cse(dot(ca, se), cross(ca, se));
+ cse[0] /= rx;
+ cse[1] /= ry;
+ double const lensq = dot(cse,cse);
+ Geom::Point csd = ( ( lensq < 4
+ ? sqrt( 1/lensq - .25 )
+ : 0.0 )
+ * cse.ccw() );
+
+ Geom::Point ra = -csd - 0.5 * cse;
+ if ( ra[0] <= -1 ) {
+ sang = M_PI;
+ } else if ( ra[0] >= 1 ) {
+ sang = 0;
+ } else {
+ sang = acos(ra[0]);
+ if ( ra[1] < 0 ) {
+ sang = 2 * M_PI - sang;
+ }
+ }
+
+ ra = -csd + 0.5 * cse;
+ if ( ra[0] <= -1 ) {
+ eang = M_PI;
+ } else if ( ra[0] >= 1 ) {
+ eang = 0;
+ } else {
+ eang = acos(ra[0]);
+ if ( ra[1] < 0 ) {
+ eang = 2 * M_PI - eang;
+ }
+ }
+
+ csd[0] *= rx;
+ csd[1] *= ry;
+ ca[1] = -ca[1]; // because it's the inverse rotation
+
+ dr[0] = dot(ca, csd);
+ dr[1] = cross(ca, csd);
+
+ ca[1] = -ca[1];
+
+ if ( wise ) {
+
+ if (large) {
+ dr = -dr;
+ double swap = eang;
+ eang = sang;
+ sang = swap;
+ eang += M_PI;
+ sang += M_PI;
+ if ( eang >= 2*M_PI ) {
+ eang -= 2*M_PI;
+ }
+ if ( sang >= 2*M_PI ) {
+ sang -= 2*M_PI;
+ }
+ }
+
+ } else {
+ if (!large) {
+ dr = -dr;
+ double swap = eang;
+ eang = sang;
+ sang = swap;
+ eang += M_PI;
+ sang += M_PI;
+ if ( eang >= 2*M_PI ) {
+ eang -= 2 * M_PI;
+ }
+ if ( sang >= 2*M_PI ) {
+ sang -= 2 * M_PI;
+ }
+ }
+ }
+
+ dr += 0.5 * (iS + iE);
+}
+
+
+
+void Path::DoArc(Geom::Point const &iS, Geom::Point const &iE,
+ double const rx, double const ry, double const angle,
+ bool const large, bool const wise, double const tresh)
+{
+ /* TODO: Check that our behaviour is standards-conformant if iS and iE are (much) further
+ apart than the diameter. Also check that we do the right thing for negative radius.
+ (Same for the other DoArc functions in this file.) */
+ if ( rx <= 0.0001 || ry <= 0.0001 || tresh <= 1e-8) {
+ return;
+ // We always add a lineto afterwards, so this is fine.
+ // [on ajoute toujours un lineto apres, donc c bon]
+ }
+
+ double sang;
+ double eang;
+ Geom::Point dr_temp;
+ ArcAnglesAndCenter(iS, iE, rx, ry, angle*M_PI/180.0, large, wise, sang, eang, dr_temp);
+ Geom::Point dr = dr_temp;
+ /* TODO: This isn't as good numerically as treating iS and iE as primary. E.g. consider
+ the case of low curvature (i.e. very large radius). */
+
+ Geom::Scale const ar(rx, ry);
+ Geom::Rotate cb(sang);
+ Geom::Rotate cbangle(angle*M_PI/180.0);
+ double max_ang = 2 * acos ( 1 - tresh / (fmax(rx, ry) ) );
+ max_ang = fmin (max_ang, M_PI / 2 );
+ int const num_sectors = abs(sang - eang) / max_ang + 1;
+
+ if (wise) {
+
+
+ if ( sang < eang ) {
+ sang += 2*M_PI;
+ }
+ double const incr = (eang - sang) / num_sectors;
+ Geom::Rotate const omega(incr);
+ for (double b = sang + incr ; b > eang ; b += incr) {
+ cb = omega * cb;
+ AddPoint( cb.vector() * ar * cbangle + dr );
+ }
+
+ } else {
+
+ if ( sang > eang ) {
+ sang -= 2*M_PI;
+ }
+ double const incr = (eang - sang) / num_sectors;
+ Geom::Rotate const omega(incr);
+ for (double b = sang + incr ; b < eang ; b += incr) {
+ cb = omega * cb;
+ AddPoint( cb.vector() * ar * cbangle + dr);
+ }
+ }
+}
+
+
+void Path::RecCubicTo( Geom::Point const &iS, Geom::Point const &isD,
+ Geom::Point const &iE, Geom::Point const &ieD,
+ double tresh, int lev, double maxL)
+{
+ // vector from start to end point
+ Geom::Point se = iE - iS;
+ // length of that vector
+ const double dC = Geom::L2(se);
+ // if the vector from start to end point is smaller than 0.01
+ if ( dC < 0.01 ) {
+ // we still need to get an idea of how far away the curve goes from the start to end line segment se
+ // for that, we measure lengths of isD and ieD
+ const double sC = dot(isD,isD);
+ const double eC = dot(ieD,ieD);
+ // if they are limited by tresh, great
+ if ( sC < tresh && eC < tresh ) {
+ return;
+ }
+ // otherwise proceed
+
+ } else {
+ // okay so length is greater than or equal to 0.01, we can still check the perpendicular component
+ // of the control handles and see if they are limited by tresh
+ const double sC = fabs(cross(se, isD)) / dC;
+ const double eC = fabs(cross(se, ieD)) / dC;
+ if ( sC < tresh && eC < tresh ) {
+ // presque tt droit -> attention si on nous demande de bien subdiviser les petits segments
+ // if the perpendicular is limited and a maxL is set, check if maxL is being respected, if yes
+ // return otherwise we split
+ if ( maxL > 0 && dC > maxL ) {
+ if ( lev <= 0 ) {
+ return;
+ }
+ // maths for splitting one cubic bezier into two
+ Geom::Point m = 0.5 * (iS + iE) + 0.125 * (isD - ieD);
+ Geom::Point md = 0.75 * (iE - iS) - 0.125 * (isD + ieD);
+
+ Geom::Point hisD = 0.5 * isD;
+ Geom::Point hieD = 0.5 * ieD;
+
+ RecCubicTo(iS, hisD, m, md, tresh, lev - 1, maxL);
+ AddPoint(m);
+ RecCubicTo(m, md, iE, hieD, tresh, lev - 1,maxL);
+ }
+ return;
+ }
+ }
+
+ if ( lev <= 0 ) {
+ return;
+ }
+
+ {
+ Geom::Point m = 0.5 * (iS + iE) + 0.125 * (isD - ieD);
+ Geom::Point md = 0.75 * (iE - iS) - 0.125 * (isD + ieD);
+
+ Geom::Point hisD = 0.5 * isD;
+ Geom::Point hieD = 0.5 * ieD;
+
+ RecCubicTo(iS, hisD, m, md, tresh, lev - 1, maxL);
+ AddPoint(m);
+ RecCubicTo(m, md, iE, hieD, tresh, lev - 1,maxL);
+ }
+}
+
+
+
+void Path::RecBezierTo(const Geom::Point &iP,
+ const Geom::Point &iS,
+ const Geom::Point &iE,
+ double tresh, int lev, double maxL)
+{
+ if ( lev <= 0 ) {
+ return;
+ }
+
+ Geom::Point ps = iS - iP;
+ Geom::Point pe = iE - iP;
+ Geom::Point se = iE - iS;
+ double s = fabs(cross(pe, ps));
+ if ( s < tresh ) {
+ const double l = L2(se);
+ if ( maxL > 0 && l > maxL ) {
+ const Geom::Point m = 0.25 * (iS + iE + 2 * iP);
+ Geom::Point md = 0.5 * (iS + iP);
+ RecBezierTo(md, iS, m, tresh, lev - 1, maxL);
+ AddPoint(m);
+ md = 0.5 * (iP + iE);
+ RecBezierTo(md, m, iE, tresh, lev - 1, maxL);
+ }
+ return;
+ }
+
+ {
+ const Geom::Point m = 0.25 * (iS + iE + 2 * iP);
+ Geom::Point md = 0.5 * (iS + iP);
+ RecBezierTo(md, iS, m, tresh, lev - 1, maxL);
+ AddPoint(m);
+ md = 0.5 * (iP + iE);
+ RecBezierTo(md, m, iE, tresh, lev - 1, maxL);
+ }
+}
+
+
+void Path::DoArc(Geom::Point const &iS, Geom::Point const &iE,
+ double const rx, double const ry, double const angle,
+ bool const large, bool const wise, double const tresh, int const piece)
+{
+ /* TODO: Check that our behaviour is standards-conformant if iS and iE are (much) further
+ apart than the diameter. Also check that we do the right thing for negative radius.
+ (Same for the other DoArc functions in this file.) */
+ if ( rx <= 0.0001 || ry <= 0.0001 || tresh <= 1e-8 ) {
+ return;
+ // We always add a lineto afterwards, so this is fine.
+ // [on ajoute toujours un lineto apres, donc c bon]
+ }
+
+ double sang;
+ double eang;
+ Geom::Point dr_temp;
+ ArcAnglesAndCenter(iS, iE, rx, ry, angle*M_PI/180.0, large, wise, sang, eang, dr_temp);
+ Geom::Point dr = dr_temp;
+ /* TODO: This isn't as good numerically as treating iS and iE as primary. E.g. consider
+ the case of low curvature (i.e. very large radius). */
+
+ Geom::Scale const ar(rx, ry);
+ Geom::Rotate cb(sang);
+ Geom::Rotate cbangle(angle*M_PI/180.0);
+
+ // max angle is basically the maximum arc angle you can have that won't create
+ // an arc that exceeds the threshold
+ double max_ang = 2 * acos ( 1 - tresh / fmax(rx, ry) );
+ max_ang = fmin (max_ang, M_PI / 2 );
+ // divide the whole arc range into sectors such that each sector
+ // is no bigger than max ang
+ int const num_sectors = abs(sang - eang) / max_ang + 1;
+
+ if (wise) {
+ if ( sang < eang ) {
+ sang += 2*M_PI;
+ }
+ double const incr = (eang - sang) / num_sectors;
+ Geom::Rotate const omega(incr);
+ for (double b = sang + incr; b > eang; b += incr) {
+ cb = omega * cb;
+ AddPoint(cb.vector() * ar * cbangle + dr, piece, (sang - b) / (sang - eang));
+ }
+
+ } else {
+
+ if ( sang > eang ) {
+ sang -= 2 * M_PI;
+ }
+ double const incr = (eang - sang) / num_sectors;
+ Geom::Rotate const omega(incr);
+ for (double b = sang + incr ; b < eang ; b += incr) {
+ cb = omega * cb;
+ AddPoint(cb.vector() * ar * cbangle + dr, piece, (b - sang) / (eang - sang));
+ }
+ }
+}
+
+void Path::RecCubicTo(Geom::Point const &iS, Geom::Point const &isD,
+ Geom::Point const &iE, Geom::Point const &ieD,
+ double tresh, int lev, double st, double et, int piece)
+{
+ const Geom::Point se = iE - iS;
+ const double dC = Geom::L2(se);
+ if ( dC < 0.01 ) {
+ const double sC = dot(isD, isD);
+ const double eC = dot(ieD, ieD);
+ if ( sC < tresh && eC < tresh ) {
+ return;
+ }
+ } else {
+ const double sC = fabs(cross(se, isD)) / dC;
+ const double eC = fabs(cross(se, ieD)) / dC;
+ if ( sC < tresh && eC < tresh ) {
+ return;
+ }
+ }
+
+ if ( lev <= 0 ) {
+ return;
+ }
+
+ Geom::Point m = 0.5 * (iS + iE) + 0.125 * (isD - ieD);
+ Geom::Point md = 0.75 * (iE - iS) - 0.125 * (isD + ieD);
+ double mt = (st + et) / 2;
+
+ Geom::Point hisD = 0.5 * isD;
+ Geom::Point hieD = 0.5 * ieD;
+
+ RecCubicTo(iS, hisD, m, md, tresh, lev - 1, st, mt, piece);
+ AddPoint(m, piece, mt);
+ RecCubicTo(m, md, iE, hieD, tresh, lev - 1, mt, et, piece);
+
+}
+
+
+
+void Path::RecBezierTo(Geom::Point const &iP,
+ Geom::Point const &iS,
+ Geom::Point const &iE,
+ double tresh, int lev, double st, double et, int piece)
+{
+ if ( lev <= 0 ) {
+ return;
+ }
+
+ Geom::Point ps = iS - iP;
+ Geom::Point pe = iE - iP;
+ const double s = fabs(cross(pe, ps));
+ if ( s < tresh ) {
+ return;
+ }
+
+ {
+ const double mt = (st + et) / 2;
+ const Geom::Point m = 0.25 * (iS + iE + 2 * iP);
+ RecBezierTo(0.5 * (iS + iP), iS, m, tresh, lev - 1, st, mt, piece);
+ AddPoint(m, piece, mt);
+ RecBezierTo(0.5 * (iP + iE), m, iE, tresh, lev - 1, mt, et, piece);
+ }
+}
+
+
+
+void Path::DoArc(Geom::Point const &iS, Geom::Point const &iE,
+ double const rx, double const ry, double const angle,
+ bool const large, bool const wise, double const /*tresh*/,
+ int const piece, offset_orig &/*orig*/)
+{
+ // Will never arrive here, as offsets are made of cubics.
+ // [on n'arrivera jamais ici, puisque les offsets sont fait de cubiques]
+ /* TODO: Check that our behaviour is standards-conformant if iS and iE are (much) further
+ apart than the diameter. Also check that we do the right thing for negative radius.
+ (Same for the other DoArc functions in this file.) */
+ if ( rx <= 0.0001 || ry <= 0.0001 ) {
+ return;
+ // We always add a lineto afterwards, so this is fine.
+ // [on ajoute toujours un lineto apres, donc c bon]
+ }
+
+ double sang;
+ double eang;
+ Geom::Point dr_temp;
+ ArcAnglesAndCenter(iS, iE, rx, ry, angle*M_PI/180.0, large, wise, sang, eang, dr_temp);
+ Geom::Point dr = dr_temp;
+ /* TODO: This isn't as good numerically as treating iS and iE as primary. E.g. consider
+ the case of low curvature (i.e. very large radius). */
+
+ Geom::Scale const ar(rx, ry);
+ Geom::Rotate cb(sang);
+ Geom::Rotate cbangle(angle*M_PI/180.0);
+ if (wise) {
+
+ double const incr = -0.1/sqrt(ar.vector().length());
+ if ( sang < eang ) {
+ sang += 2*M_PI;
+ }
+ Geom::Rotate const omega(incr);
+ for (double b = sang + incr; b > eang ;b += incr) {
+ cb = omega * cb;
+ AddPoint(cb.vector() * ar * cbangle + dr, piece, (sang - b) / (sang - eang));
+ }
+
+ } else {
+ double const incr = 0.1/sqrt(ar.vector().length());
+ if ( sang > eang ) {
+ sang -= 2*M_PI;
+ }
+ Geom::Rotate const omega(incr);
+ for (double b = sang + incr ; b < eang ; b += incr) {
+ cb = omega * cb;
+ AddPoint(cb.vector() * ar * cbangle + dr, piece, (b - sang) / (eang - sang));
+ }
+ }
+}
+
+
+void Path::RecCubicTo(Geom::Point const &iS, Geom::Point const &isD,
+ Geom::Point const &iE, Geom::Point const &ieD,
+ double tresh, int lev, double st, double et,
+ int piece, offset_orig &orig)
+{
+ const Geom::Point se = iE - iS;
+ const double dC = Geom::L2(se);
+ bool doneSub = false;
+ if ( dC < 0.01 ) {
+ const double sC = dot(isD, isD);
+ const double eC = dot(ieD, ieD);
+ if ( sC < tresh && eC < tresh ) {
+ return;
+ }
+ } else {
+ const double sC = fabs(cross(se, isD)) / dC;
+ const double eC = fabs(cross(se, ieD)) / dC;
+ if ( sC < tresh && eC < tresh ) {
+ doneSub = true;
+ }
+ }
+
+ if ( lev <= 0 ) {
+ doneSub = true;
+ }
+
+ // test des inversions
+ bool stInv = false;
+ bool enInv = false;
+ {
+ Geom::Point os_pos;
+ Geom::Point os_tgt;
+ Geom::Point oe_pos;
+ Geom::Point oe_tgt;
+
+ orig.orig->PointAndTangentAt(orig.piece, orig.tSt * (1 - st) + orig.tEn * st, os_pos, os_tgt);
+ orig.orig->PointAndTangentAt(orig.piece, orig.tSt * (1 - et) + orig.tEn * et, oe_pos, oe_tgt);
+
+
+ Geom::Point n_tgt = isD;
+ double si = dot(n_tgt, os_tgt);
+ if ( si < 0 ) {
+ stInv = true;
+ }
+ n_tgt = ieD;
+ si = dot(n_tgt, oe_tgt);
+ if ( si < 0 ) {
+ enInv = true;
+ }
+ if ( stInv && enInv ) {
+
+ AddPoint(os_pos, -1, 0.0);
+ AddPoint(iE, piece, et);
+ AddPoint(iS, piece, st);
+ AddPoint(oe_pos, -1, 0.0);
+ return;
+
+ } else if ( ( stInv && !enInv ) || ( !stInv && enInv ) ) {
+ return;
+ }
+
+ }
+
+ if ( ( !stInv && !enInv && doneSub ) || lev <= 0 ) {
+ return;
+ }
+
+ {
+ const Geom::Point m = 0.5 * (iS+iE) + 0.125 * (isD - ieD);
+ const Geom::Point md = 0.75 * (iE - iS) - 0.125 * (isD + ieD);
+ const double mt = (st + et) / 2;
+ const Geom::Point hisD = 0.5 * isD;
+ const Geom::Point hieD = 0.5 * ieD;
+
+ RecCubicTo(iS, hisD, m, md, tresh, lev - 1, st, mt, piece, orig);
+ AddPoint(m, piece, mt);
+ RecCubicTo(m, md, iE, hieD, tresh, lev - 1, mt, et, piece, orig);
+ }
+}
+
+
+
+void Path::RecBezierTo(Geom::Point const &iP, Geom::Point const &iS,Geom::Point const &iE,
+ double tresh, int lev, double st, double et,
+ int piece, offset_orig& orig)
+{
+ bool doneSub = false;
+ if ( lev <= 0 ) {
+ return;
+ }
+
+ const Geom::Point ps = iS - iP;
+ const Geom::Point pe = iE - iP;
+ const double s = fabs(cross(pe, ps));
+ if ( s < tresh ) {
+ doneSub = true ;
+ }
+
+ // test des inversions
+ bool stInv = false;
+ bool enInv = false;
+ {
+ Geom::Point os_pos;
+ Geom::Point os_tgt;
+ Geom::Point oe_pos;
+ Geom::Point oe_tgt;
+ Geom::Point n_tgt;
+ Geom::Point n_pos;
+
+ double n_len;
+ double n_rad;
+ PathDescrIntermBezierTo mid(iP);
+ PathDescrBezierTo fin(iE, 1);
+
+ TangentOnBezAt(0.0, iS, mid, fin, false, n_pos, n_tgt, n_len, n_rad);
+ orig.orig->PointAndTangentAt(orig.piece, orig.tSt * (1 - st) + orig.tEn * st, os_pos, os_tgt);
+ double si = dot(n_tgt, os_tgt);
+ if ( si < 0 ) {
+ stInv = true;
+ }
+
+ TangentOnBezAt(1.0, iS, mid, fin, false, n_pos, n_tgt, n_len, n_rad);
+ orig.orig->PointAndTangentAt(orig.piece, orig.tSt * (1 - et) + orig.tEn * et, oe_pos, oe_tgt);
+ si = dot(n_tgt, oe_tgt);
+ if ( si < 0 ) {
+ enInv = true;
+ }
+
+ if ( stInv && enInv ) {
+ AddPoint(os_pos, -1, 0.0);
+ AddPoint(iE, piece, et);
+ AddPoint(iS, piece, st);
+ AddPoint(oe_pos, -1, 0.0);
+ return;
+ }
+ }
+
+ if ( !stInv && !enInv && doneSub ) {
+ return;
+ }
+
+ {
+ double mt = (st + et) / 2;
+ Geom::Point m = 0.25 * (iS + iE + 2 * iP);
+ Geom::Point md = 0.5 * (iS + iP);
+ RecBezierTo(md, iS, m, tresh, lev - 1, st, mt, piece, orig);
+ AddPoint(m, piece, mt);
+ md = 0.5 * (iP + iE);
+ RecBezierTo(md, m, iE, tresh, lev - 1, mt, et, piece, orig);
+ }
+}
+
+
+/*
+ * put a polyline in a Shape instance, for further fun
+ * pathID is the ID you want this Path instance to be associated with, for when you're going to recompose the polyline
+ * in a path description ( you need to have prepared the back data for that, of course)
+ */
+
+void Path::Fill(Shape* dest, int pathID, bool justAdd, bool closeIfNeeded, bool invert)
+{
+ if ( dest == nullptr ) {
+ return;
+ }
+
+ if ( justAdd == false ) {
+ dest->Reset(pts.size(), pts.size());
+ }
+
+ if ( pts.size() <= 1 ) {
+ return;
+ }
+
+ int first = dest->numberOfPoints();
+
+ if ( back ) {
+ dest->MakeBackData(true);
+ }
+
+ if ( invert ) {
+ if ( back ) {
+ {
+ // invert && back && !weighted
+ for (auto & pt : pts) {
+ dest->AddPoint(pt.p);
+ }
+ int lastM = 0;
+ int curP = 1;
+ int pathEnd = 0;
+ bool closed = false;
+ int lEdge = -1;
+
+ while ( curP < int(pts.size()) ) {
+ int sbp = curP;
+ int lm = lastM;
+ int prp = pathEnd;
+
+ if ( pts[sbp].isMoveTo == polyline_moveto ) {
+
+ if ( closeIfNeeded ) {
+ if ( closed && lEdge >= 0 ) {
+ dest->DisconnectStart(lEdge);
+ dest->ConnectStart(first + lastM, lEdge);
+ } else {
+ lEdge = dest->AddEdge(first + lastM, first+pathEnd);
+ if ( lEdge >= 0 ) {
+ dest->ebData[lEdge].pathID = pathID;
+ dest->ebData[lEdge].pieceID = pts[lm].piece;
+ dest->ebData[lEdge].tSt = 1.0;
+ dest->ebData[lEdge].tEn = 0.0;
+ }
+ }
+ }
+
+ lastM = curP;
+ pathEnd = curP;
+ closed = false;
+ lEdge = -1;
+
+ } else {
+
+ if ( Geom::LInfty(pts[sbp].p - pts[prp].p) >= 0.00001 ) {
+ lEdge = dest->AddEdge(first + curP, first + pathEnd);
+ if ( lEdge >= 0 ) {
+ dest->ebData[lEdge].pathID = pathID;
+ dest->ebData[lEdge].pieceID = pts[sbp].piece;
+ if ( pts[sbp].piece == pts[prp].piece ) {
+ dest->ebData[lEdge].tSt = pts[sbp].t;
+ dest->ebData[lEdge].tEn = pts[prp].t;
+ } else {
+ dest->ebData[lEdge].tSt = pts[sbp].t;
+ dest->ebData[lEdge].tEn = 0.0;
+ }
+ }
+ pathEnd = curP;
+ if ( Geom::LInfty(pts[sbp].p - pts[lm].p) < 0.00001 ) {
+ closed = true;
+ } else {
+ closed = false;
+ }
+ }
+ }
+
+ curP++;
+ }
+
+ if ( closeIfNeeded ) {
+ if ( closed && lEdge >= 0 ) {
+ dest->DisconnectStart(lEdge);
+ dest->ConnectStart(first + lastM, lEdge);
+ } else {
+ int lm = lastM;
+ lEdge = dest->AddEdge(first + lastM, first + pathEnd);
+ if ( lEdge >= 0 ) {
+ dest->ebData[lEdge].pathID = pathID;
+ dest->ebData[lEdge].pieceID = pts[lm].piece;
+ dest->ebData[lEdge].tSt = 1.0;
+ dest->ebData[lEdge].tEn = 0.0;
+ }
+ }
+ }
+ }
+
+ } else {
+
+ {
+ // invert && !back && !weighted
+ for (auto & pt : pts) {
+ dest->AddPoint(pt.p);
+ }
+ int lastM = 0;
+ int curP = 1;
+ int pathEnd = 0;
+ bool closed = false;
+ int lEdge = -1;
+ while ( curP < int(pts.size()) ) {
+ int sbp = curP;
+ int lm = lastM;
+ int prp = pathEnd;
+ if ( pts[sbp].isMoveTo == polyline_moveto ) {
+ if ( closeIfNeeded ) {
+ if ( closed && lEdge >= 0 ) {
+ dest->DisconnectStart(lEdge);
+ dest->ConnectStart(first + lastM, lEdge);
+ } else {
+ dest->AddEdge(first + lastM, first + pathEnd);
+ }
+ }
+ lastM = curP;
+ pathEnd = curP;
+ closed = false;
+ lEdge = -1;
+ } else {
+ if ( Geom::LInfty(pts[sbp].p - pts[prp].p) >= 0.00001 ) {
+ lEdge = dest->AddEdge(first+curP, first+pathEnd);
+ pathEnd = curP;
+ if ( Geom::LInfty(pts[sbp].p - pts[lm].p) < 0.00001 ) {
+ closed = true;
+ } else {
+ closed = false;
+ }
+ }
+ }
+ curP++;
+ }
+
+ if ( closeIfNeeded ) {
+ if ( closed && lEdge >= 0 ) {
+ dest->DisconnectStart(lEdge);
+ dest->ConnectStart(first + lastM, lEdge);
+ } else {
+ dest->AddEdge(first + lastM, first + pathEnd);
+ }
+ }
+
+ }
+ }
+
+ } else {
+
+ if ( back ) {
+ {
+ // !invert && back && !weighted
+
+ // add all points to the shape
+ for (auto & pt : pts) {
+ dest->AddPoint(pt.p);
+ }
+
+ int lastM = 0;
+ int curP = 1;
+ int pathEnd = 0;
+ bool closed = false;
+ int lEdge = -1;
+ while ( curP < int(pts.size()) ) {
+ int sbp = curP;
+ int lm = lastM;
+ int prp = pathEnd;
+ if ( pts[sbp].isMoveTo == polyline_moveto ) {
+ if ( closeIfNeeded ) {
+ if ( closed && lEdge >= 0 ) {
+ dest->DisconnectEnd(lEdge);
+ dest->ConnectEnd(first + lastM, lEdge);
+ } else {
+ lEdge = dest->AddEdge(first + pathEnd, first+lastM);
+ if ( lEdge >= 0 ) {
+ dest->ebData[lEdge].pathID = pathID;
+ dest->ebData[lEdge].pieceID = pts[lm].piece;
+ dest->ebData[lEdge].tSt = 0.0;
+ dest->ebData[lEdge].tEn = 1.0;
+ }
+ }
+ }
+ lastM = curP;
+ pathEnd = curP;
+ closed = false;
+ lEdge = -1;
+ } else {
+ if ( Geom::LInfty(pts[sbp].p - pts[prp].p) >= 0.00001 ) {
+ lEdge = dest->AddEdge(first + pathEnd, first + curP);
+ dest->ebData[lEdge].pathID = pathID;
+ dest->ebData[lEdge].pieceID = pts[sbp].piece;
+ if ( pts[sbp].piece == pts[prp].piece ) {
+ dest->ebData[lEdge].tSt = pts[prp].t;
+ dest->ebData[lEdge].tEn = pts[sbp].t;
+ } else {
+ dest->ebData[lEdge].tSt = 0.0;
+ dest->ebData[lEdge].tEn = pts[sbp].t;
+ }
+ pathEnd = curP;
+ if ( Geom::LInfty(pts[sbp].p - pts[lm].p) < 0.00001 ) {
+ closed = true;
+ } else {
+ closed = false;
+ }
+ }
+ }
+ curP++;
+ }
+
+ if ( closeIfNeeded ) {
+ if ( closed && lEdge >= 0 ) {
+ dest->DisconnectEnd(lEdge);
+ dest->ConnectEnd(first + lastM, lEdge);
+ } else {
+ int lm = lastM;
+ lEdge = dest->AddEdge(first + pathEnd, first + lastM);
+ if ( lEdge >= 0 ) {
+ dest->ebData[lEdge].pathID = pathID;
+ dest->ebData[lEdge].pieceID = pts[lm].piece;
+ dest->ebData[lEdge].tSt = 0.0;
+ dest->ebData[lEdge].tEn = 1.0;
+ }
+ }
+ }
+ }
+
+ } else {
+ {
+ // !invert && !back && !weighted
+ for (auto & pt : pts) {
+ dest->AddPoint(pt.p);
+ }
+
+ int lastM = 0;
+ int curP = 1;
+ int pathEnd = 0;
+ bool closed = false;
+ int lEdge = -1;
+ while ( curP < int(pts.size()) ) {
+ int sbp = curP;
+ int lm = lastM;
+ int prp = pathEnd;
+ if ( pts[sbp].isMoveTo == polyline_moveto ) {
+ if ( closeIfNeeded ) {
+ if ( closed && lEdge >= 0 ) {
+ dest->DisconnectEnd(lEdge);
+ dest->ConnectEnd(first + lastM, lEdge);
+ } else {
+ dest->AddEdge(first + pathEnd, first + lastM);
+ }
+ }
+ lastM = curP;
+ pathEnd = curP;
+ closed = false;
+ lEdge = -1;
+ } else {
+ if ( Geom::LInfty(pts[sbp].p - pts[prp].p) >= 0.00001 ) {
+ lEdge = dest->AddEdge(first+pathEnd, first+curP);
+ pathEnd = curP;
+ if ( Geom::LInfty(pts[sbp].p - pts[lm].p) < 0.00001 ) {
+ closed = true;
+ } else {
+ closed = false;
+ }
+ }
+ }
+ curP++;
+ }
+
+ if ( closeIfNeeded ) {
+ if ( closed && lEdge >= 0 ) {
+ dest->DisconnectEnd(lEdge);
+ dest->ConnectEnd(first + lastM, lEdge);
+ } else {
+ dest->AddEdge(first + pathEnd, first + lastM);
+ }
+ }
+
+ }
+ }
+ }
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/livarot/PathCutting.cpp b/src/livarot/PathCutting.cpp
new file mode 100644
index 0000000..5978126
--- /dev/null
+++ b/src/livarot/PathCutting.cpp
@@ -0,0 +1,1529 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * PathCutting.cpp
+ * nlivarot
+ *
+ * Created by fred on someday in 2004.
+ * public domain
+ *
+ * Additional Code by Authors:
+ * Richard Hughes <cyreve@users.sf.net>
+ *
+ * Copyright (C) 2005 Richard Hughes
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <cmath>
+#include <cstring>
+#include <string>
+#include <cstdio>
+#include <typeinfo>
+#include "Path.h"
+#include "style.h"
+#include "livarot/path-description.h"
+#include <2geom/pathvector.h>
+#include <2geom/point.h>
+#include <2geom/affine.h>
+#include <2geom/sbasis-to-bezier.h>
+#include <2geom/curves.h>
+#include <vector>
+#include "helper/geom-curves.h"
+#include "helper/geom.h"
+
+#include "svg/svg.h"
+
+void Path::DashPolyline(float head,float tail,float body,int nbD, const float dashs[],bool stPlain,float stOffset)
+{
+ if ( nbD <= 0 || body <= 0.0001 ) return; // pas de tirets, en fait
+
+ std::vector<path_lineto> orig_pts = pts;
+ pts.clear();
+
+ int lastMI=-1;
+ int curP = 0;
+ int lastMP = -1;
+
+ for (int i = 0; i < int(orig_pts.size()); i++) {
+ if ( orig_pts[curP].isMoveTo == polyline_moveto ) {
+ if ( lastMI >= 0 && lastMI < i-1 ) { // au moins 2 points
+ DashSubPath(i-lastMI,lastMP, orig_pts, head,tail,body,nbD,dashs,stPlain,stOffset);
+ }
+ lastMI=i;
+ lastMP=curP;
+ }
+ curP++;
+ }
+ if ( lastMI >= 0 && lastMI < int(orig_pts.size()) - 1 ) {
+ DashSubPath(orig_pts.size() - lastMI, lastMP, orig_pts, head, tail, body, nbD, dashs, stPlain, stOffset);
+ }
+}
+
+void Path::DashPolylineFromStyle(SPStyle *style, float scale, float min_len)
+{
+ if (style->stroke_dasharray.values.empty() || !style->stroke_dasharray.is_valid()) return;
+
+ double dlen = 0.0;
+ // Find total length
+ for (auto & value : style->stroke_dasharray.values) {
+ dlen += value.value * scale;
+ }
+ if (dlen >= min_len) {
+ // Extract out dash pattern (relative positions)
+ double dash_offset = style->stroke_dashoffset.value * scale;
+ size_t n_dash = style->stroke_dasharray.values.size();
+ std::vector<double> dash(n_dash);
+ for (unsigned i = 0; i < n_dash; i++) {
+ dash[i] = style->stroke_dasharray.values[i].value * scale;
+ }
+
+ // Convert relative positions to absolute positions
+ int nbD = n_dash;
+ std::vector<float> dashes(n_dash);
+ if (dlen > 0) {
+ while (dash_offset >= dlen) dash_offset -= dlen;
+ }
+ dashes[0] = dash[0];
+ for (int i = 1; i < nbD; ++i) {
+ dashes[i] = dashes[i-1] + dash[i];
+ }
+
+ // modulo dlen
+ DashPolyline(0.0, 0.0, dlen, nbD, dashes.data(), true, dash_offset);
+ }
+}
+
+
+void Path::DashSubPath(int spL, int spP, std::vector<path_lineto> const &orig_pts, float head,float tail,float body,int nbD, const float dashs[],bool stPlain,float stOffset)
+{
+ if ( spL <= 0 || spP == -1 ) return;
+
+ double totLength=0;
+ Geom::Point lastP;
+ lastP = orig_pts[spP].p;
+ for (int i=1;i<spL;i++) {
+ Geom::Point const n = orig_pts[spP + i].p;
+ Geom::Point d=n-lastP;
+ double nl=Geom::L2(d);
+ if ( nl > 0.0001 ) {
+ totLength+=nl;
+ lastP=n;
+ }
+ }
+
+ if ( totLength <= head+tail ) return; // tout mange par la tete et la queue
+
+ double curLength=0;
+ double dashPos=0;
+ int dashInd=0;
+ bool dashPlain=false;
+ double lastT=0;
+ int lastPiece=-1;
+ lastP = orig_pts[spP].p;
+ for (int i=1;i<spL;i++) {
+ Geom::Point n;
+ int nPiece=-1;
+ double nT=0;
+ if ( back ) {
+ n = orig_pts[spP + i].p;
+ nPiece = orig_pts[spP + i].piece;
+ nT = orig_pts[spP + i].t;
+ } else {
+ n = orig_pts[spP + i].p;
+ }
+ Geom::Point d=n-lastP;
+ double nl=Geom::L2(d);
+ if ( nl > 0.0001 ) {
+ double stLength=curLength;
+ double enLength=curLength+nl;
+ // couper les bouts en trop
+ if ( curLength <= head && curLength+nl > head ) {
+ nl-=head-curLength;
+ curLength=head;
+ dashInd=0;
+ dashPos=stOffset;
+ bool nPlain=stPlain;
+ while ( dashs[dashInd] < stOffset ) {
+ dashInd++;
+ nPlain=!(nPlain);
+ if ( dashInd >= nbD ) {
+ dashPos=0;
+ dashInd=0;
+ break;
+ }
+ }
+ if ( nPlain == true && dashPlain == false ) {
+ Geom::Point p=(enLength-curLength)*lastP+(curLength-stLength)*n;
+ p/=(enLength-stLength);
+ if ( back ) {
+ double pT=0;
+ if ( nPiece == lastPiece ) {
+ pT=(lastT*(enLength-curLength)+nT*(curLength-stLength))/(enLength-stLength);
+ } else {
+ pT=(nPiece*(curLength-stLength))/(enLength-stLength);
+ }
+ AddPoint(p,nPiece,pT,true);
+ } else {
+ AddPoint(p,true);
+ }
+ } else if ( nPlain == false && dashPlain == true ) {
+ }
+ dashPlain=nPlain;
+ }
+ // faire les tirets
+ if ( curLength >= head /*&& curLength+nl <= totLength-tail*/ ) {
+ while ( curLength <= totLength-tail && nl > 0 ) {
+ if ( enLength <= totLength-tail ) nl=enLength-curLength; else nl=totLength-tail-curLength;
+ double leftInDash=body-dashPos;
+ if ( dashInd < nbD ) {
+ leftInDash=dashs[dashInd]-dashPos;
+ }
+ if ( leftInDash <= nl ) {
+ bool nPlain=false;
+ if ( dashInd < nbD ) {
+ dashPos=dashs[dashInd];
+ dashInd++;
+ if ( dashPlain ) nPlain=false; else nPlain=true;
+ } else {
+ dashInd=0;
+ dashPos=0;
+ //nPlain=stPlain;
+ nPlain=dashPlain;
+ }
+ if ( nPlain == true && dashPlain == false ) {
+ Geom::Point p=(enLength-curLength-leftInDash)*lastP+(curLength+leftInDash-stLength)*n;
+ p/=(enLength-stLength);
+ if ( back ) {
+ double pT=0;
+ if ( nPiece == lastPiece ) {
+ pT=(lastT*(enLength-curLength-leftInDash)+nT*(curLength+leftInDash-stLength))/(enLength-stLength);
+ } else {
+ pT=(nPiece*(curLength+leftInDash-stLength))/(enLength-stLength);
+ }
+ AddPoint(p,nPiece,pT,true);
+ } else {
+ AddPoint(p,true);
+ }
+ } else if ( nPlain == false && dashPlain == true ) {
+ Geom::Point p=(enLength-curLength-leftInDash)*lastP+(curLength+leftInDash-stLength)*n;
+ p/=(enLength-stLength);
+ if ( back ) {
+ double pT=0;
+ if ( nPiece == lastPiece ) {
+ pT=(lastT*(enLength-curLength-leftInDash)+nT*(curLength+leftInDash-stLength))/(enLength-stLength);
+ } else {
+ pT=(nPiece*(curLength+leftInDash-stLength))/(enLength-stLength);
+ }
+ AddPoint(p,nPiece,pT,false);
+ } else {
+ AddPoint(p,false);
+ }
+ }
+ dashPlain=nPlain;
+
+ curLength+=leftInDash;
+ nl-=leftInDash;
+ } else {
+ dashPos+=nl;
+ curLength+=nl;
+ nl=0;
+ }
+ }
+ if ( dashPlain ) {
+ if ( back ) {
+ AddPoint(n,nPiece,nT,false);
+ } else {
+ AddPoint(n,false);
+ }
+ }
+ nl=enLength-curLength;
+ }
+ if ( curLength <= totLength-tail && curLength+nl > totLength-tail ) {
+ nl=totLength-tail-curLength;
+ dashInd=0;
+ dashPos=0;
+ bool nPlain=false;
+ if ( nPlain == true && dashPlain == false ) {
+ } else if ( nPlain == false && dashPlain == true ) {
+ Geom::Point p=(enLength-curLength)*lastP+(curLength-stLength)*n;
+ p/=(enLength-stLength);
+ if ( back ) {
+ double pT=0;
+ if ( nPiece == lastPiece ) {
+ pT=(lastT*(enLength-curLength)+nT*(curLength-stLength))/(enLength-stLength);
+ } else {
+ pT=(nPiece*(curLength-stLength))/(enLength-stLength);
+ }
+ AddPoint(p,nPiece,pT,false);
+ } else {
+ AddPoint(p,false);
+ }
+ }
+ dashPlain=nPlain;
+ }
+ // continuer
+ curLength=enLength;
+ lastP=n;
+ lastPiece=nPiece;
+ lastT=nT;
+ }
+ }
+}
+
+/**
+ * Make a Geom::PathVector version of the path description.
+ *
+ * \return A PathVector copy of the path description
+ */
+Geom::PathVector
+Path::MakePathVector()
+{
+ Geom::PathVector pv;
+ Geom::Path * currentpath = nullptr;
+
+ Geom::Point lastP,bezSt,bezEn;
+ int bezNb=0;
+ for (int i=0;i<int(descr_cmd.size());i++) {
+ int const typ = descr_cmd[i]->getType();
+ switch ( typ ) {
+ case descr_close:
+ {
+ currentpath->close(true);
+ }
+ break;
+
+ case descr_lineto:
+ {
+ PathDescrLineTo *nData = dynamic_cast<PathDescrLineTo *>(descr_cmd[i]);
+ currentpath->appendNew<Geom::LineSegment>(Geom::Point(nData->p[0], nData->p[1]));
+ lastP = nData->p;
+ }
+ break;
+
+ case descr_moveto:
+ {
+ PathDescrMoveTo *nData = dynamic_cast<PathDescrMoveTo *>(descr_cmd[i]);
+ pv.push_back(Geom::Path());
+ currentpath = &pv.back();
+ currentpath->start(Geom::Point(nData->p[0], nData->p[1]));
+ lastP = nData->p;
+ }
+ break;
+
+ case descr_arcto:
+ {
+ /* TODO: add testcase for this descr_arcto case */
+ PathDescrArcTo *nData = dynamic_cast<PathDescrArcTo *>(descr_cmd[i]);
+ currentpath->appendNew<Geom::EllipticalArc>( nData->rx, nData->ry, nData->angle*M_PI/180.0, nData->large, !nData->clockwise, nData->p );
+ lastP = nData->p;
+ }
+ break;
+
+ case descr_cubicto:
+ {
+ PathDescrCubicTo *nData = dynamic_cast<PathDescrCubicTo *>(descr_cmd[i]);
+ gdouble x1=lastP[0]+0.333333*nData->start[0];
+ gdouble y1=lastP[1]+0.333333*nData->start[1];
+ gdouble x2=nData->p[0]-0.333333*nData->end[0];
+ gdouble y2=nData->p[1]-0.333333*nData->end[1];
+ gdouble x3=nData->p[0];
+ gdouble y3=nData->p[1];
+ currentpath->appendNew<Geom::CubicBezier>( Geom::Point(x1,y1) , Geom::Point(x2,y2) , Geom::Point(x3,y3) );
+ lastP = nData->p;
+ }
+ break;
+
+ case descr_bezierto:
+ {
+ PathDescrBezierTo *nData = dynamic_cast<PathDescrBezierTo *>(descr_cmd[i]);
+ if ( nData->nb <= 0 ) {
+ currentpath->appendNew<Geom::LineSegment>( Geom::Point(nData->p[0], nData->p[1]) );
+ bezNb=0;
+ } else if ( nData->nb == 1 ){
+ PathDescrIntermBezierTo *iData = dynamic_cast<PathDescrIntermBezierTo *>(descr_cmd[i+1]);
+ gdouble x1=0.333333*(lastP[0]+2*iData->p[0]);
+ gdouble y1=0.333333*(lastP[1]+2*iData->p[1]);
+ gdouble x2=0.333333*(nData->p[0]+2*iData->p[0]);
+ gdouble y2=0.333333*(nData->p[1]+2*iData->p[1]);
+ gdouble x3=nData->p[0];
+ gdouble y3=nData->p[1];
+ currentpath->appendNew<Geom::CubicBezier>( Geom::Point(x1,y1) , Geom::Point(x2,y2) , Geom::Point(x3,y3) );
+ bezNb=0;
+ } else {
+ bezSt = 2*lastP-nData->p;
+ bezEn = nData->p;
+ bezNb = nData->nb;
+ }
+ lastP = nData->p;
+ }
+ break;
+
+ case descr_interm_bezier:
+ {
+ if ( bezNb > 0 ) {
+ PathDescrIntermBezierTo *nData = dynamic_cast<PathDescrIntermBezierTo *>(descr_cmd[i]);
+ Geom::Point p_m=nData->p,p_s=0.5*(bezSt+p_m),p_e;
+ if ( bezNb > 1 ) {
+ PathDescrIntermBezierTo *iData = dynamic_cast<PathDescrIntermBezierTo *>(descr_cmd[i+1]);
+ p_e=0.5*(p_m+iData->p);
+ } else {
+ p_e=bezEn;
+ }
+
+ Geom::Point cp1=0.333333*(p_s+2*p_m),cp2=0.333333*(2*p_m+p_e);
+ gdouble x1=cp1[0];
+ gdouble y1=cp1[1];
+ gdouble x2=cp2[0];
+ gdouble y2=cp2[1];
+ gdouble x3=p_e[0];
+ gdouble y3=p_e[1];
+ currentpath->appendNew<Geom::CubicBezier>( Geom::Point(x1,y1) , Geom::Point(x2,y2) , Geom::Point(x3,y3) );
+
+ bezNb--;
+ }
+ }
+ break;
+ }
+ }
+
+ return pv;
+}
+
+void Path::AddCurve(Geom::Curve const &c)
+{
+ if( is_straight_curve(c) )
+ {
+ LineTo( c.finalPoint() );
+ }
+ /*
+ else if(Geom::QuadraticBezier const *quadratic_bezier = dynamic_cast<Geom::QuadraticBezier const *>(c)) {
+ ...
+ }
+ */
+ else if(Geom::CubicBezier const *cubic_bezier = dynamic_cast<Geom::CubicBezier const *>(&c)) {
+ Geom::Point tmp = (*cubic_bezier)[3];
+ Geom::Point tms = 3 * ((*cubic_bezier)[1] - (*cubic_bezier)[0]);
+ Geom::Point tme = 3 * ((*cubic_bezier)[3] - (*cubic_bezier)[2]);
+ CubicTo (tmp, tms, tme);
+ }
+ else if(Geom::EllipticalArc const *elliptical_arc = dynamic_cast<Geom::EllipticalArc const *>(&c)) {
+ ArcTo( elliptical_arc->finalPoint(),
+ elliptical_arc->ray(Geom::X), elliptical_arc->ray(Geom::Y),
+ elliptical_arc->rotationAngle()*180.0/M_PI, // convert from radians to degrees
+ elliptical_arc->largeArc(), !elliptical_arc->sweep() );
+ } else {
+ //this case handles sbasis as well as all other curve types
+ Geom::Path sbasis_path = Geom::cubicbezierpath_from_sbasis(c.toSBasis(), 0.1);
+
+ //recurse to convert the new path resulting from the sbasis to svgd
+ for(const auto & iter : sbasis_path) {
+ AddCurve(iter);
+ }
+ }
+}
+
+/** append is false by default: it means that the path should be resetted. If it is true, the path is not resetted and Geom::Path will be appended as a new path
+ */
+void Path::LoadPath(Geom::Path const &path, Geom::Affine const &tr, bool doTransformation, bool append)
+{
+ if (!append) {
+ SetBackData (false);
+ Reset();
+ }
+ if (path.empty())
+ return;
+
+ // TODO: this can be optimized by not generating a new path here, but doing the transform in AddCurve
+ // directly on the curve parameters
+
+ Geom::Path const pathtr = doTransformation ? path * tr : path;
+
+ MoveTo( pathtr.initialPoint() );
+
+ for(const auto & cit : pathtr) {
+ AddCurve(cit);
+ }
+
+ if (pathtr.closed()) {
+ Close();
+ }
+}
+
+void Path::LoadPathVector(Geom::PathVector const &pv)
+{
+ LoadPathVector(pv, Geom::Affine(), false);
+}
+
+void Path::LoadPathVector(Geom::PathVector const &pv, Geom::Affine const &tr, bool doTransformation)
+{
+ SetBackData (false);
+ Reset();
+
+ for(const auto & it : pv) {
+ LoadPath(it, tr, doTransformation, true);
+ }
+}
+
+/**
+ * \return Length of the lines in the pts vector.
+ */
+
+double Path::Length()
+{
+ if ( pts.empty() ) {
+ return 0;
+ }
+
+ Geom::Point lastP = pts[0].p;
+
+ double len = 0;
+ for (const auto & pt : pts) {
+
+ if ( pt.isMoveTo != polyline_moveto ) {
+ len += Geom::L2(pt.p - lastP);
+ }
+
+ lastP = pt.p;
+ }
+
+ return len;
+}
+
+
+double Path::Surface()
+{
+ if ( pts.empty() ) {
+ return 0;
+ }
+
+ Geom::Point lastM = pts[0].p;
+ Geom::Point lastP = lastM;
+
+ double surf = 0;
+ for (const auto & pt : pts) {
+
+ if ( pt.isMoveTo == polyline_moveto ) {
+ surf += Geom::cross(lastM, lastM - lastP);
+ lastP = lastM = pt.p;
+ } else {
+ surf += Geom::cross(pt.p, pt.p - lastP);
+ lastP = pt.p;
+ }
+
+ }
+
+ return surf;
+}
+
+
+Path** Path::SubPaths(int &outNb,bool killNoSurf)
+{
+ int nbRes=0;
+ Path** res=nullptr;
+ Path* curAdd=nullptr;
+
+ for (auto & i : descr_cmd) {
+ int const typ = i->getType();
+ switch ( typ ) {
+ case descr_moveto:
+ if ( curAdd ) {
+ if ( curAdd->descr_cmd.size() > 1 ) {
+ curAdd->Convert(1.0);
+ double addSurf=curAdd->Surface();
+ if ( fabs(addSurf) > 0.0001 || killNoSurf == false ) {
+ res=(Path**)g_realloc(res,(nbRes+1)*sizeof(Path*));
+ res[nbRes++]=curAdd;
+ } else {
+ delete curAdd;
+ }
+ } else {
+ delete curAdd;
+ }
+ curAdd=nullptr;
+ }
+ curAdd=new Path;
+ curAdd->SetBackData(false);
+ {
+ PathDescrMoveTo *nData = dynamic_cast<PathDescrMoveTo *>(i);
+ curAdd->MoveTo(nData->p);
+ }
+ break;
+ case descr_close:
+ {
+ curAdd->Close();
+ }
+ break;
+ case descr_lineto:
+ {
+ PathDescrLineTo *nData = dynamic_cast<PathDescrLineTo *>(i);
+ curAdd->LineTo(nData->p);
+ }
+ break;
+ case descr_cubicto:
+ {
+ PathDescrCubicTo *nData = dynamic_cast<PathDescrCubicTo *>(i);
+ curAdd->CubicTo(nData->p,nData->start,nData->end);
+ }
+ break;
+ case descr_arcto:
+ {
+ PathDescrArcTo *nData = dynamic_cast<PathDescrArcTo *>(i);
+ curAdd->ArcTo(nData->p,nData->rx,nData->ry,nData->angle,nData->large,nData->clockwise);
+ }
+ break;
+ case descr_bezierto:
+ {
+ PathDescrBezierTo *nData = dynamic_cast<PathDescrBezierTo *>(i);
+ curAdd->BezierTo(nData->p);
+ }
+ break;
+ case descr_interm_bezier:
+ {
+ PathDescrIntermBezierTo *nData = dynamic_cast<PathDescrIntermBezierTo *>(i);
+ curAdd->IntermBezierTo(nData->p);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ if ( curAdd ) {
+ if ( curAdd->descr_cmd.size() > 1 ) {
+ curAdd->Convert(1.0);
+ double addSurf=curAdd->Surface();
+ if ( fabs(addSurf) > 0.0001 || killNoSurf == false ) {
+ res=(Path**)g_realloc(res,(nbRes+1)*sizeof(Path*));
+ res[nbRes++]=curAdd;
+ } else {
+ delete curAdd;
+ }
+ } else {
+ delete curAdd;
+ }
+ }
+ curAdd=nullptr;
+
+ outNb=nbRes;
+ return res;
+}
+Path** Path::SubPathsWithNesting(int &outNb,bool killNoSurf,int nbNest,int* nesting,int* conts)
+{
+ int nbRes=0;
+ Path** res=nullptr;
+ Path* curAdd=nullptr;
+ bool increment=false;
+
+ for (int i=0;i<int(descr_cmd.size());i++) {
+ int const typ = descr_cmd[i]->getType();
+ switch ( typ ) {
+ case descr_moveto:
+ {
+ if ( curAdd && increment == false ) {
+ if ( curAdd->descr_cmd.size() > 1 ) {
+ // sauvegarder descr_cmd[0]->associated
+ int savA=curAdd->descr_cmd[0]->associated;
+ curAdd->Convert(1.0);
+ curAdd->descr_cmd[0]->associated=savA; // associated n'est pas utilise apres
+ double addSurf=curAdd->Surface();
+ if ( fabs(addSurf) > 0.0001 || killNoSurf == false ) {
+ res=(Path**)g_realloc(res,(nbRes+1)*sizeof(Path*));
+ res[nbRes++]=curAdd;
+ } else {
+ delete curAdd;
+ }
+ } else {
+ delete curAdd;
+ }
+ curAdd=nullptr;
+ }
+ Path* hasParent=nullptr;
+ for (int j=0;j<nbNest;j++) {
+ if ( conts[j] == i && nesting[j] >= 0 ) {
+ int parentMvt=conts[nesting[j]];
+ for (int k=0;k<nbRes;k++) {
+ if ( res[k] && res[k]->descr_cmd.empty() == false && res[k]->descr_cmd[0]->associated == parentMvt ) {
+ hasParent=res[k];
+ break;
+ }
+ }
+ }
+ if ( conts[j] > i ) break;
+ }
+ if ( hasParent ) {
+ curAdd=hasParent;
+ increment=true;
+ } else {
+ curAdd=new Path;
+ curAdd->SetBackData(false);
+ increment=false;
+ }
+ PathDescrMoveTo *nData = dynamic_cast<PathDescrMoveTo *>(descr_cmd[i]);
+ int mNo=curAdd->MoveTo(nData->p);
+ curAdd->descr_cmd[mNo]->associated=i;
+ }
+ break;
+ case descr_close:
+ {
+ curAdd->Close();
+ }
+ break;
+ case descr_lineto:
+ {
+ PathDescrLineTo *nData = dynamic_cast<PathDescrLineTo *>(descr_cmd[i]);
+ curAdd->LineTo(nData->p);
+ }
+ break;
+ case descr_cubicto:
+ {
+ PathDescrCubicTo *nData = dynamic_cast<PathDescrCubicTo *>(descr_cmd[i]);
+ curAdd->CubicTo(nData->p,nData->start,nData->end);
+ }
+ break;
+ case descr_arcto:
+ {
+ PathDescrArcTo *nData = dynamic_cast<PathDescrArcTo *>(descr_cmd[i]);
+ curAdd->ArcTo(nData->p,nData->rx,nData->ry,nData->angle,nData->large,nData->clockwise);
+ }
+ break;
+ case descr_bezierto:
+ {
+ PathDescrBezierTo *nData = dynamic_cast<PathDescrBezierTo *>(descr_cmd[i]);
+ curAdd->BezierTo(nData->p);
+ }
+ break;
+ case descr_interm_bezier:
+ {
+ PathDescrIntermBezierTo *nData = dynamic_cast<PathDescrIntermBezierTo *>(descr_cmd[i]);
+ curAdd->IntermBezierTo(nData->p);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ if ( curAdd && increment == false ) {
+ if ( curAdd->descr_cmd.size() > 1 ) {
+ curAdd->Convert(1.0);
+ double addSurf=curAdd->Surface();
+ if ( fabs(addSurf) > 0.0001 || killNoSurf == false ) {
+ res=(Path**)g_realloc(res,(nbRes+1)*sizeof(Path*));
+ res[nbRes++]=curAdd;
+ } else {
+ delete curAdd;
+ }
+ } else {
+ delete curAdd;
+ }
+ }
+ curAdd=nullptr;
+
+ outNb=nbRes;
+ return res;
+}
+
+
+void Path::ConvertForcedToVoid()
+{
+ for (int i=0; i < int(descr_cmd.size()); i++) {
+ if ( descr_cmd[i]->getType() == descr_forced) {
+ delete descr_cmd[i];
+ descr_cmd.erase(descr_cmd.begin() + i);
+ }
+ }
+}
+
+
+void Path::ConvertForcedToMoveTo()
+{
+ Geom::Point lastSeen(0, 0);
+ Geom::Point lastMove(0, 0);
+
+ {
+ Geom::Point lastPos(0, 0);
+ for (int i = int(descr_cmd.size()) - 1; i >= 0; i--) {
+ int const typ = descr_cmd[i]->getType();
+ switch ( typ ) {
+ case descr_forced:
+ {
+ PathDescrForced *d = dynamic_cast<PathDescrForced *>(descr_cmd[i]);
+ d->p = lastPos;
+ break;
+ }
+ case descr_close:
+ {
+ PathDescrClose *d = dynamic_cast<PathDescrClose *>(descr_cmd[i]);
+ d->p = lastPos;
+ break;
+ }
+ case descr_moveto:
+ {
+ PathDescrMoveTo *d = dynamic_cast<PathDescrMoveTo *>(descr_cmd[i]);
+ lastPos = d->p;
+ break;
+ }
+ case descr_lineto:
+ {
+ PathDescrLineTo *d = dynamic_cast<PathDescrLineTo *>(descr_cmd[i]);
+ lastPos = d->p;
+ break;
+ }
+ case descr_arcto:
+ {
+ PathDescrArcTo *d = dynamic_cast<PathDescrArcTo *>(descr_cmd[i]);
+ lastPos = d->p;
+ break;
+ }
+ case descr_cubicto:
+ {
+ PathDescrCubicTo *d = dynamic_cast<PathDescrCubicTo *>(descr_cmd[i]);
+ lastPos = d->p;
+ break;
+ }
+ case descr_bezierto:
+ {
+ PathDescrBezierTo *d = dynamic_cast<PathDescrBezierTo *>(descr_cmd[i]);
+ lastPos = d->p;
+ break;
+ }
+ case descr_interm_bezier:
+ {
+ PathDescrIntermBezierTo *d = dynamic_cast<PathDescrIntermBezierTo *>(descr_cmd[i]);
+ lastPos = d->p;
+ break;
+ }
+ default:
+ break;
+ }
+ }
+ }
+
+ bool hasMoved = false;
+ for (int i = 0; i < int(descr_cmd.size()); i++) {
+ int const typ = descr_cmd[i]->getType();
+ switch ( typ ) {
+ case descr_forced:
+ if ( i < int(descr_cmd.size()) - 1 && hasMoved ) { // sinon il termine le chemin
+
+ delete descr_cmd[i];
+ descr_cmd[i] = new PathDescrMoveTo(lastSeen);
+ lastMove = lastSeen;
+ hasMoved = true;
+ }
+ break;
+
+ case descr_moveto:
+ {
+ PathDescrMoveTo *nData = dynamic_cast<PathDescrMoveTo *>(descr_cmd[i]);
+ lastMove = lastSeen = nData->p;
+ hasMoved = true;
+ }
+ break;
+ case descr_close:
+ {
+ lastSeen=lastMove;
+ }
+ break;
+ case descr_lineto:
+ {
+ PathDescrLineTo *nData = dynamic_cast<PathDescrLineTo *>(descr_cmd[i]);
+ lastSeen=nData->p;
+ }
+ break;
+ case descr_cubicto:
+ {
+ PathDescrCubicTo *nData = dynamic_cast<PathDescrCubicTo *>(descr_cmd[i]);
+ lastSeen=nData->p;
+ }
+ break;
+ case descr_arcto:
+ {
+ PathDescrArcTo *nData = dynamic_cast<PathDescrArcTo *>(descr_cmd[i]);
+ lastSeen=nData->p;
+ }
+ break;
+ case descr_bezierto:
+ {
+ PathDescrBezierTo *nData = dynamic_cast<PathDescrBezierTo *>(descr_cmd[i]);
+ lastSeen=nData->p;
+ }
+ break;
+ case descr_interm_bezier:
+ {
+ PathDescrIntermBezierTo *nData = dynamic_cast<PathDescrIntermBezierTo *>(descr_cmd[i]);
+ lastSeen=nData->p;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+}
+static int CmpPosition(const void * p1, const void * p2) {
+ Path::cut_position *cp1=(Path::cut_position*)p1;
+ Path::cut_position *cp2=(Path::cut_position*)p2;
+ if ( cp1->piece < cp2->piece ) return -1;
+ if ( cp1->piece > cp2->piece ) return 1;
+ if ( cp1->t < cp2->t ) return -1;
+ if ( cp1->t > cp2->t ) return 1;
+ return 0;
+}
+static int CmpCurv(const void * p1, const void * p2) {
+ double *cp1=(double*)p1;
+ double *cp2=(double*)p2;
+ if ( *cp1 < *cp2 ) return -1;
+ if ( *cp1 > *cp2 ) return 1;
+ return 0;
+}
+
+
+Path::cut_position* Path::CurvilignToPosition(int nbCv, double *cvAbs, int &nbCut)
+{
+ if ( nbCv <= 0 || pts.empty() || back == false ) {
+ return nullptr;
+ }
+
+ qsort(cvAbs, nbCv, sizeof(double), CmpCurv);
+
+ cut_position *res = nullptr;
+ nbCut = 0;
+ int curCv = 0;
+
+ double len = 0;
+ double lastT = 0;
+ int lastPiece = -1;
+
+ Geom::Point lastM = pts[0].p;
+ Geom::Point lastP = lastM;
+
+ for (const auto & pt : pts) {
+
+ if ( pt.isMoveTo == polyline_moveto ) {
+
+ lastP = lastM = pt.p;
+ lastT = pt.t;
+ lastPiece = pt.piece;
+
+ } else {
+
+ double const add = Geom::L2(pt.p - lastP);
+ double curPos = len;
+ double curAdd = add;
+
+ while ( curAdd > 0.0001 && curCv < nbCv && curPos + curAdd >= cvAbs[curCv] ) {
+ double const theta = (cvAbs[curCv] - len) / add;
+ res = (cut_position*) g_realloc(res, (nbCut + 1) * sizeof(cut_position));
+ res[nbCut].piece = pt.piece;
+ res[nbCut].t = theta * pt.t + (1 - theta) * ( (lastPiece != pt.piece) ? 0 : lastT);
+ nbCut++;
+ curAdd -= cvAbs[curCv] - curPos;
+ curPos = cvAbs[curCv];
+ curCv++;
+ }
+
+ len += add;
+ lastPiece = pt.piece;
+ lastP = pt.p;
+ lastT = pt.t;
+ }
+ }
+
+ return res;
+}
+
+/*
+Moved from Layout-TNG-OutIter.cpp
+TODO: clean up uses of the original function and remove
+
+Original Comment:
+"this function really belongs to Path. I'll probably move it there eventually,
+hence the Path-esque coding style"
+
+*/
+template<typename T> inline static T square(T x) {return x*x;}
+Path::cut_position Path::PointToCurvilignPosition(Geom::Point const &pos, unsigned seg) const
+{
+ // if the parameter "seg" == 0, then all segments will be considered
+ // In however e.g. "seg" == 6 , then only the 6th segment will be considered
+
+ unsigned bestSeg = 0;
+ double bestRangeSquared = DBL_MAX;
+ double bestT = 0.0; // you need a sentinel, or make sure that you prime with correct values.
+
+ for (unsigned i = 1 ; i < pts.size() ; i++) {
+ if (pts[i].isMoveTo == polyline_moveto || (seg > 0 && i != seg)) continue;
+ Geom::Point p1, p2, localPos;
+ double thisRangeSquared;
+ double t;
+
+ if (pts[i - 1].p == pts[i].p) {
+ thisRangeSquared = square(pts[i].p[Geom::X] - pos[Geom::X]) + square(pts[i].p[Geom::Y] - pos[Geom::Y]);
+ t = 0.0;
+ } else {
+ // we rotate all our coordinates so we're always looking at a mostly vertical line.
+ if (fabs(pts[i - 1].p[Geom::X] - pts[i].p[Geom::X]) < fabs(pts[i - 1].p[Geom::Y] - pts[i].p[Geom::Y])) {
+ p1 = pts[i - 1].p;
+ p2 = pts[i].p;
+ localPos = pos;
+ } else {
+ p1 = pts[i - 1].p.cw();
+ p2 = pts[i].p.cw();
+ localPos = pos.cw();
+ }
+ double gradient = (p2[Geom::X] - p1[Geom::X]) / (p2[Geom::Y] - p1[Geom::Y]);
+ double intersection = p1[Geom::X] - gradient * p1[Geom::Y];
+ /*
+ orthogonalGradient = -1.0 / gradient; // you are going to have numerical problems here.
+ orthogonalIntersection = localPos[Geom::X] - orthogonalGradient * localPos[Geom::Y];
+ nearestY = (orthogonalIntersection - intersection) / (gradient - orthogonalGradient);
+
+ expand out nearestY fully :
+ nearestY = (localPos[Geom::X] - (-1.0 / gradient) * localPos[Geom::Y] - intersection) / (gradient - (-1.0 / gradient));
+
+ multiply top and bottom by gradient:
+ nearestY = (localPos[Geom::X] * gradient - (-1.0) * localPos[Geom::Y] - intersection * gradient) / (gradient * gradient - (-1.0));
+
+ and simplify to get:
+ */
+ double nearestY = (localPos[Geom::X] * gradient + localPos[Geom::Y] - intersection * gradient)
+ / (gradient * gradient + 1.0);
+ t = (nearestY - p1[Geom::Y]) / (p2[Geom::Y] - p1[Geom::Y]);
+ if (t <= 0.0) {
+ thisRangeSquared = square(p1[Geom::X] - localPos[Geom::X]) + square(p1[Geom::Y] - localPos[Geom::Y]);
+ t = 0.0;
+ } else if (t >= 1.0) {
+ thisRangeSquared = square(p2[Geom::X] - localPos[Geom::X]) + square(p2[Geom::Y] - localPos[Geom::Y]);
+ t = 1.0;
+ } else {
+ thisRangeSquared = square(nearestY * gradient + intersection - localPos[Geom::X]) + square(nearestY - localPos[Geom::Y]);
+ }
+ }
+
+ if (thisRangeSquared < bestRangeSquared) {
+ bestSeg = i;
+ bestRangeSquared = thisRangeSquared;
+ bestT = t;
+ }
+ }
+ Path::cut_position result;
+ if (bestSeg == 0) {
+ result.piece = 0;
+ result.t = 0.0;
+ } else {
+ result.piece = pts[bestSeg].piece;
+ if (result.piece == pts[bestSeg - 1].piece) {
+ result.t = pts[bestSeg - 1].t * (1.0 - bestT) + pts[bestSeg].t * bestT;
+ } else {
+ result.t = pts[bestSeg].t * bestT;
+ }
+ }
+ return result;
+}
+/*
+ this one also belongs to Path
+ returns the length of the path up to the position indicated by t (0..1)
+
+ TODO: clean up uses of the original function and remove
+
+ should this take a cut_position as a parameter?
+*/
+double Path::PositionToLength(int piece, double t)
+{
+ double length = 0.0;
+ for (unsigned i = 1 ; i < pts.size() ; i++) {
+ if (pts[i].isMoveTo == polyline_moveto) continue;
+ if (pts[i].piece == piece && t < pts[i].t) {
+ length += Geom::L2((t - pts[i - 1].t) / (pts[i].t - pts[i - 1].t) * (pts[i].p - pts[i - 1].p));
+ break;
+ }
+ length += Geom::L2(pts[i].p - pts[i - 1].p);
+ }
+ return length;
+}
+
+void Path::ConvertPositionsToForced(int nbPos, cut_position *poss)
+{
+ if ( nbPos <= 0 ) {
+ return;
+ }
+
+ {
+ Geom::Point lastPos(0, 0);
+ for (int i = int(descr_cmd.size()) - 1; i >= 0; i--) {
+ int const typ = descr_cmd[i]->getType();
+ switch ( typ ) {
+
+ case descr_forced:
+ {
+ PathDescrForced *d = dynamic_cast<PathDescrForced *>(descr_cmd[i]);
+ d->p = lastPos;
+ break;
+ }
+
+ case descr_close:
+ {
+ delete descr_cmd[i];
+ descr_cmd[i] = new PathDescrLineTo(Geom::Point(0, 0));
+
+ int fp = i - 1;
+ while ( fp >= 0 && (descr_cmd[fp]->getType()) != descr_moveto ) {
+ fp--;
+ }
+
+ if ( fp >= 0 ) {
+ PathDescrMoveTo *oData = dynamic_cast<PathDescrMoveTo *>(descr_cmd[fp]);
+ dynamic_cast<PathDescrLineTo*>(descr_cmd[i])->p = oData->p;
+ }
+ }
+ break;
+
+ case descr_bezierto:
+ {
+ PathDescrBezierTo *nData = dynamic_cast<PathDescrBezierTo *>(descr_cmd[i]);
+ Geom::Point theP = nData->p;
+ if ( nData->nb == 0 ) {
+ lastPos = theP;
+ }
+ }
+ break;
+
+ case descr_moveto:
+ {
+ PathDescrMoveTo *d = dynamic_cast<PathDescrMoveTo *>(descr_cmd[i]);
+ lastPos = d->p;
+ break;
+ }
+ case descr_lineto:
+ {
+ PathDescrLineTo *d = dynamic_cast<PathDescrLineTo *>(descr_cmd[i]);
+ lastPos = d->p;
+ break;
+ }
+ case descr_arcto:
+ {
+ PathDescrArcTo *d = dynamic_cast<PathDescrArcTo *>(descr_cmd[i]);
+ lastPos = d->p;
+ break;
+ }
+ case descr_cubicto:
+ {
+ PathDescrCubicTo *d = dynamic_cast<PathDescrCubicTo *>(descr_cmd[i]);
+ lastPos = d->p;
+ break;
+ }
+ case descr_interm_bezier:
+ {
+ PathDescrIntermBezierTo *d = dynamic_cast<PathDescrIntermBezierTo *>(descr_cmd[i]);
+ lastPos = d->p;
+ break;
+ }
+ default:
+ break;
+ }
+ }
+ }
+ if (descr_cmd[0]->getType() == descr_moveto)
+ descr_flags |= descr_doing_subpath; // see LP Bug 166302
+
+ qsort(poss, nbPos, sizeof(cut_position), CmpPosition);
+
+ for (int curP=0;curP<nbPos;curP++) {
+ int cp=poss[curP].piece;
+ if ( cp < 0 || cp >= int(descr_cmd.size()) ) break;
+ float ct=poss[curP].t;
+ if ( ct < 0 ) continue;
+ if ( ct > 1 ) continue;
+
+ int const typ = descr_cmd[cp]->getType();
+ if ( typ == descr_moveto || typ == descr_forced || typ == descr_close ) {
+ // ponctuel= rien a faire
+ } else if ( typ == descr_lineto || typ == descr_arcto || typ == descr_cubicto ) {
+ // facile: creation d'un morceau et d'un forced -> 2 commandes
+ Geom::Point theP;
+ Geom::Point theT;
+ Geom::Point startP;
+ startP=PrevPoint(cp-1);
+ if ( typ == descr_cubicto ) {
+ double len,rad;
+ Geom::Point stD,enD,endP;
+ {
+ PathDescrCubicTo *oData = dynamic_cast<PathDescrCubicTo *>(descr_cmd[cp]);
+ stD=oData->start;
+ enD=oData->end;
+ endP=oData->p;
+ TangentOnCubAt (ct, startP, *oData,true, theP,theT,len,rad);
+ }
+
+ theT*=len;
+
+ InsertCubicTo(endP,(1-ct)*theT,(1-ct)*enD,cp+1);
+ InsertForcePoint(cp+1);
+ {
+ PathDescrCubicTo *nData = dynamic_cast<PathDescrCubicTo *>(descr_cmd[cp]);
+ nData->start=ct*stD;
+ nData->end=ct*theT;
+ nData->p=theP;
+ }
+ // decalages dans le tableau des positions de coupe
+ for (int j=curP+1;j<nbPos;j++) {
+ if ( poss[j].piece == cp ) {
+ poss[j].piece+=2;
+ poss[j].t=(poss[j].t-ct)/(1-ct);
+ } else {
+ poss[j].piece+=2;
+ }
+ }
+ } else if ( typ == descr_lineto ) {
+ Geom::Point endP;
+ {
+ PathDescrLineTo *oData = dynamic_cast<PathDescrLineTo *>(descr_cmd[cp]);
+ endP=oData->p;
+ }
+
+ theP=ct*endP+(1-ct)*startP;
+
+ InsertLineTo(endP,cp+1);
+ InsertForcePoint(cp+1);
+ {
+ PathDescrLineTo *nData = dynamic_cast<PathDescrLineTo *>(descr_cmd[cp]);
+ nData->p=theP;
+ }
+ // decalages dans le tableau des positions de coupe
+ for (int j=curP+1;j<nbPos;j++) {
+ if ( poss[j].piece == cp ) {
+ poss[j].piece+=2;
+ poss[j].t=(poss[j].t-ct)/(1-ct);
+ } else {
+ poss[j].piece+=2;
+ }
+ }
+ } else if ( typ == descr_arcto ) {
+ Geom::Point endP;
+ double rx,ry,angle;
+ bool clockw,large;
+ double delta=0;
+ {
+ PathDescrArcTo *oData = dynamic_cast<PathDescrArcTo *>(descr_cmd[cp]);
+ endP=oData->p;
+ rx=oData->rx;
+ ry=oData->ry;
+ angle=oData->angle;
+ clockw=oData->clockwise;
+ large=oData->large;
+ }
+ {
+ double sang,eang;
+ ArcAngles(startP,endP,rx,ry,angle*M_PI/180.0,large,clockw,sang,eang);
+
+ if (clockw) {
+ if ( sang < eang ) sang += 2*M_PI;
+ delta=eang-sang;
+ } else {
+ if ( sang > eang ) sang -= 2*M_PI;
+ delta=eang-sang;
+ }
+ if ( delta < 0 ) delta=-delta;
+ }
+
+ PointAt (cp,ct, theP);
+
+ if ( delta*(1-ct) > M_PI ) {
+ InsertArcTo(endP,rx,ry,angle,true,clockw,cp+1);
+ } else {
+ InsertArcTo(endP,rx,ry,angle,false,clockw,cp+1);
+ }
+ InsertForcePoint(cp+1);
+ {
+ PathDescrArcTo *nData = dynamic_cast<PathDescrArcTo *>(descr_cmd[cp]);
+ nData->p=theP;
+ if ( delta*ct > M_PI ) {
+ nData->large=true;
+ } else {
+ nData->large=false;
+ }
+ }
+ // decalages dans le tableau des positions de coupe
+ for (int j=curP+1;j<nbPos;j++) {
+ if ( poss[j].piece == cp ) {
+ poss[j].piece+=2;
+ poss[j].t=(poss[j].t-ct)/(1-ct);
+ } else {
+ poss[j].piece+=2;
+ }
+ }
+ }
+ } else if ( typ == descr_bezierto || typ == descr_interm_bezier ) {
+ // dur
+ int theBDI=cp;
+ while ( theBDI >= 0 && (descr_cmd[theBDI]->getType()) != descr_bezierto ) theBDI--;
+ if ( (descr_cmd[theBDI]->getType()) == descr_bezierto ) {
+ PathDescrBezierTo theBD=*(dynamic_cast<PathDescrBezierTo *>(descr_cmd[theBDI]));
+ if ( cp >= theBDI && cp < theBDI+theBD.nb ) {
+ if ( theBD.nb == 1 ) {
+ Geom::Point endP=theBD.p;
+ Geom::Point midP;
+ Geom::Point startP;
+ startP=PrevPoint(theBDI-1);
+ {
+ PathDescrIntermBezierTo *nData = dynamic_cast<PathDescrIntermBezierTo *>(descr_cmd[theBDI+1]);
+ midP=nData->p;
+ }
+ Geom::Point aP=ct*midP+(1-ct)*startP;
+ Geom::Point bP=ct*endP+(1-ct)*midP;
+ Geom::Point knotP=ct*bP+(1-ct)*aP;
+
+ InsertIntermBezierTo(bP,theBDI+2);
+ InsertBezierTo(knotP,1,theBDI+2);
+ InsertForcePoint(theBDI+2);
+ {
+ PathDescrIntermBezierTo *nData = dynamic_cast<PathDescrIntermBezierTo *>(descr_cmd[theBDI+1]);
+ nData->p=aP;
+ }
+ {
+ PathDescrBezierTo *nData = dynamic_cast<PathDescrBezierTo *>(descr_cmd[theBDI]);
+ nData->p=knotP;
+ }
+ // decalages dans le tableau des positions de coupe
+ for (int j=curP+1;j<nbPos;j++) {
+ if ( poss[j].piece == cp ) {
+ poss[j].piece+=3;
+ poss[j].t=(poss[j].t-ct)/(1-ct);
+ } else {
+ poss[j].piece+=3;
+ }
+ }
+
+ } else {
+ // decouper puis repasser
+ if ( cp > theBDI ) {
+ Geom::Point pcP,ncP;
+ {
+ PathDescrIntermBezierTo *nData = dynamic_cast<PathDescrIntermBezierTo *>(descr_cmd[cp]);
+ pcP=nData->p;
+ }
+ {
+ PathDescrIntermBezierTo *nData = dynamic_cast<PathDescrIntermBezierTo *>(descr_cmd[cp+1]);
+ ncP=nData->p;
+ }
+ Geom::Point knotP=0.5*(pcP+ncP);
+
+ InsertBezierTo(knotP,theBD.nb-(cp-theBDI),cp+1);
+ {
+ PathDescrBezierTo *nData = dynamic_cast<PathDescrBezierTo *>(descr_cmd[theBDI]);
+ nData->nb=cp-theBDI;
+ }
+
+ // decalages dans le tableau des positions de coupe
+ for (int j=curP;j<nbPos;j++) {
+ if ( poss[j].piece == cp ) {
+ poss[j].piece+=1;
+ } else {
+ poss[j].piece+=1;
+ }
+ }
+ curP--;
+ } else {
+ Geom::Point pcP,ncP;
+ {
+ PathDescrIntermBezierTo *nData = dynamic_cast<PathDescrIntermBezierTo *>(descr_cmd[cp+1]);
+ pcP=nData->p;
+ }
+ {
+ PathDescrIntermBezierTo *nData = dynamic_cast<PathDescrIntermBezierTo *>(descr_cmd[cp+2]);
+ ncP=nData->p;
+ }
+ Geom::Point knotP=0.5*(pcP+ncP);
+
+ InsertBezierTo(knotP,theBD.nb-1,cp+2);
+ {
+ PathDescrBezierTo *nData = dynamic_cast<PathDescrBezierTo *>(descr_cmd[theBDI]);
+ nData->nb=1;
+ }
+
+ // decalages dans le tableau des positions de coupe
+ for (int j=curP;j<nbPos;j++) {
+ if ( poss[j].piece == cp ) {
+// poss[j].piece+=1;
+ } else {
+ poss[j].piece+=1;
+ }
+ }
+ curP--;
+ }
+ }
+ } else {
+ // on laisse aussi tomber
+ }
+ } else {
+ // on laisse tomber
+ }
+ }
+ }
+}
+
+void Path::ConvertPositionsToMoveTo(int nbPos,cut_position* poss)
+{
+ ConvertPositionsToForced(nbPos,poss);
+// ConvertForcedToMoveTo();
+ // on fait une version customizee a la place
+
+ Path* res=new Path;
+
+ Geom::Point lastP(0,0);
+ for (int i=0;i<int(descr_cmd.size());i++) {
+ int const typ = descr_cmd[i]->getType();
+ if ( typ == descr_moveto ) {
+ Geom::Point np;
+ {
+ PathDescrMoveTo *nData = dynamic_cast<PathDescrMoveTo *>(descr_cmd[i]);
+ np=nData->p;
+ }
+ Geom::Point endP;
+ bool hasClose=false;
+ int hasForced=-1;
+ bool doesClose=false;
+ int j=i+1;
+ for (;j<int(descr_cmd.size());j++) {
+ int const ntyp = descr_cmd[j]->getType();
+ if ( ntyp == descr_moveto ) {
+ j--;
+ break;
+ } else if ( ntyp == descr_forced ) {
+ if ( hasForced < 0 ) hasForced=j;
+ } else if ( ntyp == descr_close ) {
+ hasClose=true;
+ break;
+ } else if ( ntyp == descr_lineto ) {
+ PathDescrLineTo *nData = dynamic_cast<PathDescrLineTo *>(descr_cmd[j]);
+ endP=nData->p;
+ } else if ( ntyp == descr_arcto ) {
+ PathDescrArcTo *nData = dynamic_cast<PathDescrArcTo *>(descr_cmd[j]);
+ endP=nData->p;
+ } else if ( ntyp == descr_cubicto ) {
+ PathDescrCubicTo *nData = dynamic_cast<PathDescrCubicTo *>(descr_cmd[j]);
+ endP=nData->p;
+ } else if ( ntyp == descr_bezierto ) {
+ PathDescrBezierTo *nData = dynamic_cast<PathDescrBezierTo *>(descr_cmd[j]);
+ endP=nData->p;
+ } else {
+ }
+ }
+ if ( Geom::LInfty(endP-np) < 0.00001 ) {
+ doesClose=true;
+ }
+ if ( ( doesClose || hasClose ) && hasForced >= 0 ) {
+ // printf("nasty i=%i j=%i frc=%i\n",i,j,hasForced);
+ // aghhh.
+ Geom::Point nMvtP=PrevPoint(hasForced);
+ res->MoveTo(nMvtP);
+ Geom::Point nLastP=nMvtP;
+ for (int k = hasForced + 1; k < j; k++) {
+ int ntyp=descr_cmd[k]->getType();
+ if ( ntyp == descr_moveto ) {
+ // ne doit pas arriver
+ } else if ( ntyp == descr_forced ) {
+ res->MoveTo(nLastP);
+ } else if ( ntyp == descr_close ) {
+ // rien a faire ici; de plus il ne peut y en avoir qu'un
+ } else if ( ntyp == descr_lineto ) {
+ PathDescrLineTo *nData = dynamic_cast<PathDescrLineTo *>(descr_cmd[k]);
+ res->LineTo(nData->p);
+ nLastP=nData->p;
+ } else if ( ntyp == descr_arcto ) {
+ PathDescrArcTo *nData = dynamic_cast<PathDescrArcTo *>(descr_cmd[k]);
+ res->ArcTo(nData->p,nData->rx,nData->ry,nData->angle,nData->large,nData->clockwise);
+ nLastP=nData->p;
+ } else if ( ntyp == descr_cubicto ) {
+ PathDescrCubicTo *nData = dynamic_cast<PathDescrCubicTo *>(descr_cmd[k]);
+ res->CubicTo(nData->p,nData->start,nData->end);
+ nLastP=nData->p;
+ } else if ( ntyp == descr_bezierto ) {
+ PathDescrBezierTo *nData = dynamic_cast<PathDescrBezierTo *>(descr_cmd[k]);
+ res->BezierTo(nData->p);
+ nLastP=nData->p;
+ } else if ( ntyp == descr_interm_bezier ) {
+ PathDescrIntermBezierTo *nData = dynamic_cast<PathDescrIntermBezierTo *>(descr_cmd[k]);
+ res->IntermBezierTo(nData->p);
+ } else {
+ }
+ }
+ if ( doesClose == false ) res->LineTo(np);
+ nLastP=np;
+ for (int k=i+1;k<hasForced;k++) {
+ int ntyp=descr_cmd[k]->getType();
+ if ( ntyp == descr_moveto ) {
+ // ne doit pas arriver
+ } else if ( ntyp == descr_forced ) {
+ res->MoveTo(nLastP);
+ } else if ( ntyp == descr_close ) {
+ // rien a faire ici; de plus il ne peut y en avoir qu'un
+ } else if ( ntyp == descr_lineto ) {
+ PathDescrLineTo *nData = dynamic_cast<PathDescrLineTo *>(descr_cmd[k]);
+ res->LineTo(nData->p);
+ nLastP=nData->p;
+ } else if ( ntyp == descr_arcto ) {
+ PathDescrArcTo *nData = dynamic_cast<PathDescrArcTo *>(descr_cmd[k]);
+ res->ArcTo(nData->p,nData->rx,nData->ry,nData->angle,nData->large,nData->clockwise);
+ nLastP=nData->p;
+ } else if ( ntyp == descr_cubicto ) {
+ PathDescrCubicTo *nData = dynamic_cast<PathDescrCubicTo *>(descr_cmd[k]);
+ res->CubicTo(nData->p,nData->start,nData->end);
+ nLastP=nData->p;
+ } else if ( ntyp == descr_bezierto ) {
+ PathDescrBezierTo *nData = dynamic_cast<PathDescrBezierTo *>(descr_cmd[k]);
+ res->BezierTo(nData->p);
+ nLastP=nData->p;
+ } else if ( ntyp == descr_interm_bezier ) {
+ PathDescrIntermBezierTo *nData = dynamic_cast<PathDescrIntermBezierTo *>(descr_cmd[k]);
+ res->IntermBezierTo(nData->p);
+ } else {
+ }
+ }
+ lastP=nMvtP;
+ i=j;
+ } else {
+ // regular, just move on
+ res->MoveTo(np);
+ lastP=np;
+ }
+ } else if ( typ == descr_close ) {
+ res->Close();
+ } else if ( typ == descr_forced ) {
+ res->MoveTo(lastP);
+ } else if ( typ == descr_lineto ) {
+ PathDescrLineTo *nData = dynamic_cast<PathDescrLineTo *>(descr_cmd[i]);
+ res->LineTo(nData->p);
+ lastP=nData->p;
+ } else if ( typ == descr_arcto ) {
+ PathDescrArcTo *nData = dynamic_cast<PathDescrArcTo *>(descr_cmd[i]);
+ res->ArcTo(nData->p,nData->rx,nData->ry,nData->angle,nData->large,nData->clockwise);
+ lastP=nData->p;
+ } else if ( typ == descr_cubicto ) {
+ PathDescrCubicTo *nData = dynamic_cast<PathDescrCubicTo *>(descr_cmd[i]);
+ res->CubicTo(nData->p,nData->start,nData->end);
+ lastP=nData->p;
+ } else if ( typ == descr_bezierto ) {
+ PathDescrBezierTo *nData = dynamic_cast<PathDescrBezierTo *>(descr_cmd[i]);
+ res->BezierTo(nData->p);
+ lastP=nData->p;
+ } else if ( typ == descr_interm_bezier ) {
+ PathDescrIntermBezierTo *nData = dynamic_cast<PathDescrIntermBezierTo *>(descr_cmd[i]);
+ res->IntermBezierTo(nData->p);
+ } else {
+ }
+ }
+
+ Copy(res);
+ delete res;
+ return;
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/livarot/PathOutline.cpp b/src/livarot/PathOutline.cpp
new file mode 100644
index 0000000..c93c040
--- /dev/null
+++ b/src/livarot/PathOutline.cpp
@@ -0,0 +1,1530 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * TODO: insert short description here
+ *//*
+ * Authors: see git history
+ *
+ * Copyright (C) 2018 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "livarot/Path.h"
+#include "livarot/path-description.h"
+
+/*
+ * the "outliner"
+ * takes a sequence of path commands and produces a set of commands that approximates the offset
+ * result is stored in dest (that paremeter is handed to all the subfunctions)
+ * not that the result is in general not mathematically correct; you can end up with unwanted holes in your
+ * beautiful offset. a better way is to do path->polyline->polygon->offset of polygon->polyline(=contours of the polygon)->path
+ * but computing offsets of the path is faster...
+ */
+
+// outline of a path.
+// computed by making 2 offsets, one of the "left" side of the path, one of the right side, and then glueing the two
+// the left side has to be reversed to make a contour
+void Path::Outline(Path *dest, double width, JoinType join, ButtType butt, double miter)
+{
+ if ( descr_flags & descr_adding_bezier ) {
+ CancelBezier();
+ }
+ if ( descr_flags & descr_doing_subpath ) {
+ CloseSubpath();
+ }
+ if ( descr_cmd.size() <= 1 ) {
+ return;
+ }
+ if ( dest == nullptr ) {
+ return;
+ }
+
+ dest->Reset();
+ dest->SetBackData(false);
+
+ outline_callbacks calls;
+ Geom::Point endButt;
+ Geom::Point endPos;
+ calls.cubicto = StdCubicTo;
+ calls.bezierto = StdBezierTo;
+ calls.arcto = StdArcTo;
+
+ Path *rev = new Path;
+
+ // we repeat the offset contour creation for each subpath
+ int curP = 0;
+ do {
+ int lastM = curP;
+ do {
+ curP++;
+ if (curP >= int(descr_cmd.size())) {
+ break;
+ }
+ int typ = descr_cmd[curP]->getType();
+ if (typ == descr_moveto) {
+ break;
+ }
+ } while (curP < int(descr_cmd.size()));
+
+ if (curP >= int(descr_cmd.size())) {
+ curP = descr_cmd.size();
+ }
+
+ if (curP > lastM + 1) {
+ // we have isolated a subpath, now we make a reversed version of it
+ // we do so by taking the subpath in the reverse and constructing a path as appropriate
+ // the construct is stored in "rev"
+ int curD = curP - 1;
+ Geom::Point curX;
+ Geom::Point nextX;
+ int firstTyp = descr_cmd[curD]->getType();
+ bool const needClose = (firstTyp == descr_close);
+ while (curD > lastM && descr_cmd[curD]->getType() == descr_close) {
+ curD--;
+ }
+
+ int realP = curD + 1;
+ if (curD > lastM) {
+ curX = PrevPoint(curD);
+ rev->Reset ();
+ rev->MoveTo(curX);
+ while (curD > lastM) {
+ int const typ = descr_cmd[curD]->getType();
+ if (typ == descr_moveto) {
+ // rev->Close();
+ curD--;
+ } else if (typ == descr_forced) {
+ // rev->Close();
+ curD--;
+ } else if (typ == descr_lineto) {
+ nextX = PrevPoint (curD - 1);
+ rev->LineTo (nextX);
+ curX = nextX;
+ curD--;
+ } else if (typ == descr_cubicto) {
+ PathDescrCubicTo* nData = dynamic_cast<PathDescrCubicTo*>(descr_cmd[curD]);
+ nextX = PrevPoint (curD - 1);
+ Geom::Point isD=-nData->start;
+ Geom::Point ieD=-nData->end;
+ rev->CubicTo (nextX, ieD,isD);
+ curX = nextX;
+ curD--;
+ } else if (typ == descr_arcto) {
+ PathDescrArcTo* nData = dynamic_cast<PathDescrArcTo*>(descr_cmd[curD]);
+ nextX = PrevPoint (curD - 1);
+ rev->ArcTo (nextX, nData->rx,nData->ry,nData->angle,nData->large,!nData->clockwise);
+ curX = nextX;
+ curD--;
+ } else if (typ == descr_bezierto) {
+ nextX = PrevPoint (curD - 1);
+ rev->LineTo (nextX);
+ curX = nextX;
+ curD--;
+ } else if (typ == descr_interm_bezier) {
+ int nD = curD - 1;
+ while (nD > lastM && descr_cmd[nD]->getType() != descr_bezierto) nD--;
+ if ((descr_cmd[nD]->getType()) != descr_bezierto) {
+ // pas trouve le debut!?
+ // Not find the start?!
+ nextX = PrevPoint (nD);
+ rev->LineTo (nextX);
+ curX = nextX;
+ } else {
+ nextX = PrevPoint (nD - 1);
+ rev->BezierTo (nextX);
+ for (int i = curD; i > nD; i--) {
+ PathDescrIntermBezierTo* nData = dynamic_cast<PathDescrIntermBezierTo*>(descr_cmd[i]);
+ rev->IntermBezierTo (nData->p);
+ }
+ rev->EndBezierTo ();
+ curX = nextX;
+ }
+ curD = nD - 1;
+ } else {
+ curD--;
+ }
+ }
+
+ // offset the paths and glue everything
+ // actual offseting is done in SubContractOutline()
+ if (needClose) {
+ rev->Close ();
+ rev->SubContractOutline (0, rev->descr_cmd.size(),
+ dest, calls, 0.0025 * width * width, width,
+ join, butt, miter, true, false, endPos, endButt);
+ SubContractOutline (lastM, realP + 1 - lastM,
+ dest, calls, 0.0025 * width * width,
+ width, join, butt, miter, true, false, endPos, endButt);
+ } else {
+ rev->SubContractOutline (0, rev->descr_cmd.size(),
+ dest, calls, 0.0025 * width * width, width,
+ join, butt, miter, false, false, endPos, endButt);
+ Geom::Point endNor=endButt.ccw();
+ if (butt == butt_round) {
+ dest->ArcTo (endPos+width*endButt, width, width, 0.0, false, true);
+ dest->ArcTo (endPos+width*endNor, width, width, 0.0, false, true);
+ } else if (butt == butt_square) {
+ dest->LineTo (endPos-width*endNor+width*endButt);
+ dest->LineTo (endPos+width*endNor+width*endButt);
+ dest->LineTo (endPos+width*endNor);
+ } else if (butt == butt_pointy) {
+ dest->LineTo (endPos+width*endButt);
+ dest->LineTo (endPos+width*endNor);
+ } else {
+ dest->LineTo (endPos+width*endNor);
+ }
+ SubContractOutline (lastM, realP - lastM,
+ dest, calls, 0.0025 * width * width, width, join, butt,
+ miter, false, true, endPos, endButt);
+
+ endNor=endButt.ccw();
+ if (butt == butt_round) {
+ dest->ArcTo (endPos+width*endButt, width, width, 0.0, false, true);
+ dest->ArcTo (endPos+width*endNor, width, width, 0.0, false, true);
+ } else if (butt == butt_square) {
+ dest->LineTo (endPos-width*endNor+width*endButt);
+ dest->LineTo (endPos+width*endNor+width*endButt);
+ dest->LineTo (endPos+width*endNor);
+ } else if (butt == butt_pointy) {
+ dest->LineTo (endPos+width*endButt);
+ dest->LineTo (endPos+width*endNor);
+ } else {
+ dest->LineTo (endPos+width*endNor);
+ }
+ dest->Close ();
+ }
+ } // if (curD > lastM)
+ } // if (curP > lastM + 1)
+
+ } while (curP < int(descr_cmd.size()));
+
+ delete rev;
+}
+
+// versions for outlining closed path: they only make one side of the offset contour
+void
+Path::OutsideOutline (Path * dest, double width, JoinType join, ButtType butt,
+ double miter)
+{
+ if (descr_flags & descr_adding_bezier) {
+ CancelBezier();
+ }
+ if (descr_flags & descr_doing_subpath) {
+ CloseSubpath();
+ }
+ if (int(descr_cmd.size()) <= 1) return;
+ if (dest == nullptr) return;
+ dest->Reset ();
+ dest->SetBackData (false);
+
+ outline_callbacks calls;
+ Geom::Point endButt, endPos;
+ calls.cubicto = StdCubicTo;
+ calls.bezierto = StdBezierTo;
+ calls.arcto = StdArcTo;
+ SubContractOutline (0, descr_cmd.size(),
+ dest, calls, 0.0025 * width * width, width, join, butt,
+ miter, true, false, endPos, endButt);
+}
+
+void
+Path::InsideOutline (Path * dest, double width, JoinType join, ButtType butt,
+ double miter)
+{
+ if ( descr_flags & descr_adding_bezier ) {
+ CancelBezier();
+ }
+ if ( descr_flags & descr_doing_subpath ) {
+ CloseSubpath();
+ }
+ if (int(descr_cmd.size()) <= 1) return;
+ if (dest == nullptr) return;
+ dest->Reset ();
+ dest->SetBackData (false);
+
+ outline_callbacks calls;
+ Geom::Point endButt, endPos;
+ calls.cubicto = StdCubicTo;
+ calls.bezierto = StdBezierTo;
+ calls.arcto = StdArcTo;
+
+ Path *rev = new Path;
+
+ int curP = 0;
+ do {
+ int lastM = curP;
+ do {
+ curP++;
+ if (curP >= int(descr_cmd.size())) break;
+ int typ = descr_cmd[curP]->getType();
+ if (typ == descr_moveto) break;
+ } while (curP < int(descr_cmd.size()));
+ if (curP >= int(descr_cmd.size())) curP = descr_cmd.size();
+ if (curP > lastM + 1) {
+ // Otherwise there's only one point. (tr: or "only a point")
+ // [sinon il n'y a qu'un point]
+ int curD = curP - 1;
+ Geom::Point curX;
+ Geom::Point nextX;
+ while (curD > lastM && (descr_cmd[curD]->getType()) == descr_close) curD--;
+ if (curD > lastM) {
+ curX = PrevPoint (curD);
+ rev->Reset ();
+ rev->MoveTo (curX);
+ while (curD > lastM) {
+ int typ = descr_cmd[curD]->getType();
+ if (typ == descr_moveto) {
+ rev->Close ();
+ curD--;
+ } else if (typ == descr_forced) {
+ curD--;
+ } else if (typ == descr_lineto) {
+ nextX = PrevPoint (curD - 1);
+ rev->LineTo (nextX);
+ curX = nextX;
+ curD--;
+ } else if (typ == descr_cubicto) {
+ PathDescrCubicTo *nData = dynamic_cast<PathDescrCubicTo*>(descr_cmd[curD]);
+ nextX = PrevPoint (curD - 1);
+ Geom::Point isD=-nData->start;
+ Geom::Point ieD=-nData->end;
+ rev->CubicTo (nextX, ieD,isD);
+ curX = nextX;
+ curD--;
+ } else if (typ == descr_arcto) {
+ PathDescrArcTo* nData = dynamic_cast<PathDescrArcTo*>(descr_cmd[curD]);
+ nextX = PrevPoint (curD - 1);
+ rev->ArcTo (nextX, nData->rx,nData->ry,nData->angle,nData->large,nData->clockwise);
+ curX = nextX;
+ curD--;
+ } else if (typ == descr_bezierto) {
+ nextX = PrevPoint (curD - 1);
+ rev->LineTo (nextX);
+ curX = nextX;
+ curD--;
+ } else if (typ == descr_interm_bezier) {
+ int nD = curD - 1;
+ while (nD > lastM && (descr_cmd[nD]->getType()) != descr_bezierto) nD--;
+ if (descr_cmd[nD]->getType() != descr_bezierto) {
+ // pas trouve le debut!?
+ nextX = PrevPoint (nD);
+ rev->LineTo (nextX);
+ curX = nextX;
+ } else {
+ nextX = PrevPoint (nD - 1);
+ rev->BezierTo (nextX);
+ for (int i = curD; i > nD; i--) {
+ PathDescrIntermBezierTo* nData = dynamic_cast<PathDescrIntermBezierTo*>(descr_cmd[i]);
+ rev->IntermBezierTo (nData->p);
+ }
+ rev->EndBezierTo ();
+ curX = nextX;
+ }
+ curD = nD - 1;
+ } else {
+ curD--;
+ }
+ }
+ rev->Close ();
+ rev->SubContractOutline (0, rev->descr_cmd.size(),
+ dest, calls, 0.0025 * width * width,
+ width, join, butt, miter, true, false,
+ endPos, endButt);
+ }
+ }
+ } while (curP < int(descr_cmd.size()));
+
+ delete rev;
+}
+
+
+// the offset
+// take each command and offset it.
+// the bezier spline is split in a sequence of bezier curves, and these are transformed in cubic bezier (which is
+// not hard since they are quadratic bezier)
+// joins are put where needed
+void Path::SubContractOutline(int off, int num_pd,
+ Path *dest, outline_callbacks & calls,
+ double tolerance, double width, JoinType join,
+ ButtType /*butt*/, double miter, bool closeIfNeeded,
+ bool skipMoveto, Geom::Point &lastP, Geom::Point &lastT)
+{
+ outline_callback_data callsData;
+
+ callsData.orig = this;
+ callsData.dest = dest;
+ int curP = 1;
+
+ // le moveto
+ Geom::Point curX;
+ {
+ int firstTyp = descr_cmd[off]->getType();
+ if ( firstTyp != descr_moveto ) {
+ curX[0] = curX[1] = 0;
+ curP = 0;
+ } else {
+ PathDescrMoveTo* nData = dynamic_cast<PathDescrMoveTo*>(descr_cmd[off]);
+ curX = nData->p;
+ }
+ }
+ Geom::Point curT(0, 0);
+
+ bool doFirst = true;
+ Geom::Point firstP(0, 0);
+ Geom::Point firstT(0, 0);
+
+ // et le reste, 1 par 1
+ while (curP < num_pd)
+ {
+ int curD = off + curP;
+ int nType = descr_cmd[curD]->getType();
+ Geom::Point nextX;
+ Geom::Point stPos, enPos, stTgt, enTgt, stNor, enNor;
+ double stRad, enRad, stTle, enTle;
+ if (nType == descr_forced) {
+ curP++;
+ } else if (nType == descr_moveto) {
+ PathDescrMoveTo* nData = dynamic_cast<PathDescrMoveTo*>(descr_cmd[curD]);
+ nextX = nData->p;
+ // et on avance
+ if (doFirst) {
+ } else {
+ if (closeIfNeeded) {
+ if ( Geom::LInfty (curX- firstP) < 0.0001 ) {
+ OutlineJoin (dest, firstP, curT, firstT, width, join,
+ miter, nType);
+ dest->Close ();
+ } else {
+ PathDescrLineTo temp(firstP);
+
+ TangentOnSegAt (0.0, curX, temp, stPos, stTgt,
+ stTle);
+ TangentOnSegAt (1.0, curX, temp, enPos, enTgt,
+ enTle);
+ stNor=stTgt.cw();
+ enNor=enTgt.cw();
+
+ // jointure
+ {
+ Geom::Point pos;
+ pos = curX;
+ OutlineJoin (dest, pos, curT, stNor, width, join,
+ miter, nType);
+ }
+ dest->LineTo (enPos+width*enNor);
+
+ // jointure
+ {
+ Geom::Point pos;
+ pos = firstP;
+ OutlineJoin (dest, enPos, enNor, firstT, width, join,
+ miter, nType);
+ dest->Close ();
+ }
+ }
+ }
+ }
+ firstP = nextX;
+ curP++;
+ }
+ else if (nType == descr_close)
+ {
+ if (! doFirst)
+ {
+ if (Geom::LInfty (curX - firstP) < 0.0001)
+ {
+ OutlineJoin (dest, firstP, curT, firstT, width, join,
+ miter, nType);
+ dest->Close ();
+ }
+ else
+ {
+ PathDescrLineTo temp(firstP);
+ nextX = firstP;
+
+ TangentOnSegAt (0.0, curX, temp, stPos, stTgt, stTle);
+ TangentOnSegAt (1.0, curX, temp, enPos, enTgt, enTle);
+ stNor=stTgt.cw();
+ enNor=enTgt.cw();
+
+ // jointure
+ {
+ OutlineJoin (dest, stPos, curT, stNor, width, join,
+ miter, nType);
+ }
+
+ dest->LineTo (enPos+width*enNor);
+
+ // jointure
+ {
+ OutlineJoin (dest, enPos, enNor, firstT, width, join,
+ miter, nType);
+ dest->Close ();
+ }
+ }
+ }
+ doFirst = true;
+ curP++;
+ }
+ else if (nType == descr_lineto)
+ {
+ PathDescrLineTo* nData = dynamic_cast<PathDescrLineTo*>(descr_cmd[curD]);
+ nextX = nData->p;
+ // et on avance
+ TangentOnSegAt (0.0, curX, *nData, stPos, stTgt, stTle);
+ TangentOnSegAt (1.0, curX, *nData, enPos, enTgt, enTle);
+ // test de nullité du segment
+ if (IsNulCurve (descr_cmd, curD, curX))
+ {
+ if (descr_cmd.size() == 2) { // single point, see LP Bug 1006666
+ stTgt = dest->descr_cmd.size() ? Geom::Point(1, 0) : Geom::Point(-1, 0); // reverse direction
+ enTgt = stTgt;
+ } else {
+ curP++;
+ continue;
+ }
+ }
+ stNor=stTgt.cw();
+ enNor=enTgt.cw();
+
+ lastP = enPos;
+ lastT = enTgt;
+
+ if (doFirst)
+ {
+ doFirst = false;
+ firstP = stPos;
+ firstT = stNor;
+ if (skipMoveto)
+ {
+ skipMoveto = false;
+ }
+ else
+ dest->MoveTo (curX+width*stNor);
+ }
+ else
+ {
+ // jointure
+ Geom::Point pos;
+ pos = curX;
+ OutlineJoin (dest, pos, curT, stNor, width, join, miter, nType);
+ }
+
+ int n_d = dest->LineTo (nextX+width*enNor);
+ if (n_d >= 0)
+ {
+ dest->descr_cmd[n_d]->associated = curP;
+ dest->descr_cmd[n_d]->tSt = 0.0;
+ dest->descr_cmd[n_d]->tEn = 1.0;
+ }
+ curP++;
+ }
+ else if (nType == descr_cubicto)
+ {
+ PathDescrCubicTo* nData = dynamic_cast<PathDescrCubicTo*>(descr_cmd[curD]);
+ nextX = nData->p;
+ // test de nullite du segment
+ if (IsNulCurve (descr_cmd, curD, curX))
+ {
+ curP++;
+ continue;
+ }
+ // et on avance
+ TangentOnCubAt (0.0, curX, *nData, false, stPos, stTgt,
+ stTle, stRad);
+ TangentOnCubAt (1.0, curX, *nData, true, enPos, enTgt,
+ enTle, enRad);
+ stNor=stTgt.cw();
+ enNor=enTgt.cw();
+
+ lastP = enPos;
+ lastT = enTgt;
+
+ if (doFirst)
+ {
+ doFirst = false;
+ firstP = stPos;
+ firstT = stNor;
+ if (skipMoveto)
+ {
+ skipMoveto = false;
+ }
+ else
+ dest->MoveTo (curX+width*stNor);
+ }
+ else
+ {
+ // jointure
+ Geom::Point pos;
+ pos = curX;
+ OutlineJoin (dest, pos, curT, stNor, width, join, miter, nType);
+ }
+
+ callsData.piece = curP;
+ callsData.tSt = 0.0;
+ callsData.tEn = 1.0;
+ callsData.x1 = curX[0];
+ callsData.y1 = curX[1];
+ callsData.x2 = nextX[0];
+ callsData.y2 = nextX[1];
+ callsData.d.c.dx1 = nData->start[0];
+ callsData.d.c.dy1 = nData->start[1];
+ callsData.d.c.dx2 = nData->end[0];
+ callsData.d.c.dy2 = nData->end[1];
+ (calls.cubicto) (&callsData, tolerance, width);
+
+ curP++;
+ }
+ else if (nType == descr_arcto)
+ {
+ PathDescrArcTo* nData = dynamic_cast<PathDescrArcTo*>(descr_cmd[curD]);
+ nextX = nData->p;
+ // test de nullité du segment
+ if (IsNulCurve (descr_cmd, curD, curX))
+ {
+ curP++;
+ continue;
+ }
+ // et on avance
+ TangentOnArcAt (0.0, curX, *nData, stPos, stTgt, stTle,
+ stRad);
+ TangentOnArcAt (1.0, curX, *nData, enPos, enTgt, enTle,
+ enRad);
+ stNor=stTgt.cw();
+ enNor=enTgt.cw();
+
+ lastP = enPos;
+ lastT = enTgt; // tjs definie
+
+ if (doFirst)
+ {
+ doFirst = false;
+ firstP = stPos;
+ firstT = stNor;
+ if (skipMoveto)
+ {
+ skipMoveto = false;
+ }
+ else
+ dest->MoveTo (curX+width*stNor);
+ }
+ else
+ {
+ // jointure
+ Geom::Point pos;
+ pos = curX;
+ OutlineJoin (dest, pos, curT, stNor, width, join, miter, nType);
+ }
+
+ callsData.piece = curP;
+ callsData.tSt = 0.0;
+ callsData.tEn = 1.0;
+ callsData.x1 = curX[0];
+ callsData.y1 = curX[1];
+ callsData.x2 = nextX[0];
+ callsData.y2 = nextX[1];
+ callsData.d.a.rx = nData->rx;
+ callsData.d.a.ry = nData->ry;
+ callsData.d.a.angle = nData->angle;
+ callsData.d.a.clock = nData->clockwise;
+ callsData.d.a.large = nData->large;
+ (calls.arcto) (&callsData, tolerance, width);
+
+ curP++;
+ }
+ else if (nType == descr_bezierto)
+ {
+ PathDescrBezierTo* nBData = dynamic_cast<PathDescrBezierTo*>(descr_cmd[curD]);
+ int nbInterm = nBData->nb;
+ nextX = nBData->p;
+
+ if (IsNulCurve (descr_cmd, curD, curX)) {
+ curP += nbInterm + 1;
+ continue;
+ }
+
+ curP++;
+
+ curD = off + curP;
+ int ip = curD;
+ PathDescrIntermBezierTo* nData = dynamic_cast<PathDescrIntermBezierTo*>(descr_cmd[ip]);
+
+ if (nbInterm <= 0) {
+ // et on avance
+ PathDescrLineTo temp(nextX);
+ TangentOnSegAt (0.0, curX, temp, stPos, stTgt, stTle);
+ TangentOnSegAt (1.0, curX, temp, enPos, enTgt, enTle);
+ stNor=stTgt.cw();
+ enNor=enTgt.cw();
+
+ lastP = enPos;
+ lastT = enTgt;
+
+ if (doFirst) {
+ doFirst = false;
+ firstP = stPos;
+ firstT = stNor;
+ if (skipMoveto) {
+ skipMoveto = false;
+ } else dest->MoveTo (curX+width*stNor);
+ } else {
+ // jointure
+ Geom::Point pos;
+ pos = curX;
+ if (stTle > 0) OutlineJoin (dest, pos, curT, stNor, width, join, miter, nType);
+ }
+ int n_d = dest->LineTo (nextX+width*enNor);
+ if (n_d >= 0) {
+ dest->descr_cmd[n_d]->associated = curP - 1;
+ dest->descr_cmd[n_d]->tSt = 0.0;
+ dest->descr_cmd[n_d]->tEn = 1.0;
+ }
+ } else if (nbInterm == 1) {
+ Geom::Point midX;
+ midX = nData->p;
+ // et on avance
+ TangentOnBezAt (0.0, curX, *nData, *nBData, false, stPos, stTgt, stTle, stRad);
+ TangentOnBezAt (1.0, curX, *nData, *nBData, true, enPos, enTgt, enTle, enRad);
+ stNor=stTgt.cw();
+ enNor=enTgt.cw();
+
+ lastP = enPos;
+ lastT = enTgt;
+
+ if (doFirst) {
+ doFirst = false;
+ firstP = stPos;
+ firstT = stNor;
+ if (skipMoveto) {
+ skipMoveto = false;
+ } else dest->MoveTo (curX+width*stNor);
+ } else {
+ // jointure
+ Geom::Point pos;
+ pos = curX;
+ OutlineJoin (dest, pos, curT, stNor, width, join, miter, nType);
+ }
+
+ callsData.piece = curP;
+ callsData.tSt = 0.0;
+ callsData.tEn = 1.0;
+ callsData.x1 = curX[0];
+ callsData.y1 = curX[1];
+ callsData.x2 = nextX[0];
+ callsData.y2 = nextX[1];
+ callsData.d.b.mx = midX[0];
+ callsData.d.b.my = midX[1];
+ (calls.bezierto) (&callsData, tolerance, width);
+
+ } else if (nbInterm > 1) {
+ Geom::Point bx=curX;
+ Geom::Point cx=curX;
+ Geom::Point dx=nData->p;
+
+ TangentOnBezAt (0.0, curX, *nData, *nBData, false, stPos, stTgt, stTle, stRad);
+ stNor=stTgt.cw();
+
+ ip++;
+ nData = dynamic_cast<PathDescrIntermBezierTo*>(descr_cmd[ip]);
+ // et on avance
+ if (stTle > 0) {
+ if (doFirst) {
+ doFirst = false;
+ firstP = stPos;
+ firstT = stNor;
+ if (skipMoveto) {
+ skipMoveto = false;
+ } else dest->MoveTo (curX+width*stNor);
+ } else {
+ // jointure
+ Geom::Point pos=curX;
+ OutlineJoin (dest, pos, stTgt, stNor, width, join, miter, nType);
+ // dest->LineTo(curX+width*stNor.x,curY+width*stNor.y);
+ }
+ }
+
+ cx = 2 * bx - dx;
+
+ for (int k = 0; k < nbInterm - 1; k++) {
+ bx = cx;
+ cx = dx;
+
+ dx = nData->p;
+ ip++;
+ nData = dynamic_cast<PathDescrIntermBezierTo*>(descr_cmd[ip]);
+ Geom::Point stx = (bx + cx) / 2;
+ // double stw=(bw+cw)/2;
+
+ PathDescrBezierTo tempb((cx + dx) / 2, 1);
+ PathDescrIntermBezierTo tempi(cx);
+ TangentOnBezAt (1.0, stx, tempi, tempb, true, enPos, enTgt, enTle, enRad);
+ enNor=enTgt.cw();
+
+ lastP = enPos;
+ lastT = enTgt;
+
+ callsData.piece = curP + k;
+ callsData.tSt = 0.0;
+ callsData.tEn = 1.0;
+ callsData.x1 = stx[0];
+ callsData.y1 = stx[1];
+ callsData.x2 = (cx[0] + dx[0]) / 2;
+ callsData.y2 = (cx[1] + dx[1]) / 2;
+ callsData.d.b.mx = cx[0];
+ callsData.d.b.my = cx[1];
+ (calls.bezierto) (&callsData, tolerance, width);
+ }
+ {
+ bx = cx;
+ cx = dx;
+
+ dx = nextX;
+ dx = 2 * dx - cx;
+
+ Geom::Point stx = (bx + cx) / 2;
+ // double stw=(bw+cw)/2;
+
+ PathDescrBezierTo tempb((cx + dx) / 2, 1);
+ PathDescrIntermBezierTo tempi(cx);
+ TangentOnBezAt (1.0, stx, tempi, tempb, true, enPos,
+ enTgt, enTle, enRad);
+ enNor=enTgt.cw();
+
+ lastP = enPos;
+ lastT = enTgt;
+
+ callsData.piece = curP + nbInterm - 1;
+ callsData.tSt = 0.0;
+ callsData.tEn = 1.0;
+ callsData.x1 = stx[0];
+ callsData.y1 = stx[1];
+ callsData.x2 = (cx[0] + dx[0]) / 2;
+ callsData.y2 = (cx[1] + dx[1]) / 2;
+ callsData.d.b.mx = cx[0];
+ callsData.d.b.my = cx[1];
+ (calls.bezierto) (&callsData, tolerance, width);
+
+ }
+ }
+
+ // et on avance
+ curP += nbInterm;
+ }
+ curX = nextX;
+ curT = enNor; // sera tjs bien definie
+ }
+ if (closeIfNeeded)
+ {
+ if (! doFirst)
+ {
+ }
+ }
+
+}
+
+/*
+ *
+ * utilitaires pour l'outline
+ *
+ */
+
+// like the name says: check whether the path command is actually more than a dumb point.
+bool
+Path::IsNulCurve (std::vector<PathDescr*> const &cmd, int curD, Geom::Point const &curX)
+{
+ switch(cmd[curD]->getType()) {
+ case descr_lineto:
+ {
+ PathDescrLineTo *nData = dynamic_cast<PathDescrLineTo*>(cmd[curD]);
+ if (Geom::LInfty(nData->p - curX) < 0.00001) {
+ return true;
+ }
+ return false;
+ }
+ case descr_cubicto:
+ {
+ PathDescrCubicTo *nData = dynamic_cast<PathDescrCubicTo*>(cmd[curD]);
+ Geom::Point A = nData->start + nData->end + 2*(curX - nData->p);
+ Geom::Point B = 3*(nData->p - curX) - 2*nData->start - nData->end;
+ Geom::Point C = nData->start;
+ if (Geom::LInfty(A) < 0.0001
+ && Geom::LInfty(B) < 0.0001
+ && Geom::LInfty (C) < 0.0001) {
+ return true;
+ }
+ return false;
+ }
+ case descr_arcto:
+ {
+ PathDescrArcTo* nData = dynamic_cast<PathDescrArcTo*>(cmd[curD]);
+ if ( Geom::LInfty(nData->p - curX) < 0.00001) {
+ if ((! nData->large)
+ || (fabs (nData->rx) < 0.00001
+ || fabs (nData->ry) < 0.00001)) {
+ return true;
+ }
+ }
+ return false;
+ }
+ case descr_bezierto:
+ {
+ PathDescrBezierTo* nBData = dynamic_cast<PathDescrBezierTo*>(cmd[curD]);
+ if (nBData->nb <= 0)
+ {
+ if (Geom::LInfty(nBData->p - curX) < 0.00001) {
+ return true;
+ }
+ return false;
+ }
+ else if (nBData->nb == 1)
+ {
+ if (Geom::LInfty(nBData->p - curX) < 0.00001) {
+ int ip = curD + 1;
+ PathDescrIntermBezierTo* nData = dynamic_cast<PathDescrIntermBezierTo*>(cmd[ip]);
+ if (Geom::LInfty(nData->p - curX) < 0.00001) {
+ return true;
+ }
+ }
+ return false;
+ } else if (Geom::LInfty(nBData->p - curX) < 0.00001) {
+ for (int i = 1; i <= nBData->nb; i++) {
+ int ip = curD + i;
+ PathDescrIntermBezierTo* nData = dynamic_cast<PathDescrIntermBezierTo*>(cmd[ip]);
+ if (Geom::LInfty(nData->p - curX) > 0.00001) {
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+ default:
+ return true;
+ }
+}
+
+// tangents and curvarture computing, for the different path command types.
+// the need for tangent is obvious: it gives the normal, along which we offset points
+// curvature is used to do strength correction on the length of the tangents to the offset (see
+// cubic offset)
+
+/**
+ * \param at Distance along a tangent (0 <= at <= 1).
+ * \param iS Start point.
+ * \param fin LineTo description containing end point.
+ * \param pos Filled in with the position of `at' on the segment.
+ * \param tgt Filled in with the normalised tangent vector.
+ * \param len Filled in with the length of the segment.
+ */
+
+void Path::TangentOnSegAt(double at, Geom::Point const &iS, PathDescrLineTo const &fin,
+ Geom::Point &pos, Geom::Point &tgt, double &len)
+{
+ Geom::Point const iE = fin.p;
+ Geom::Point const seg = iE - iS;
+ double const l = L2(seg);
+ if (l <= 0.000001) {
+ pos = iS;
+ tgt = Geom::Point(0, 0);
+ len = 0;
+ } else {
+ tgt = seg / l;
+ pos = (1 - at) * iS + at * iE; // in other words, pos = iS + at * seg
+ len = l;
+ }
+}
+
+// barf
+void Path::TangentOnArcAt(double at, const Geom::Point &iS, PathDescrArcTo const &fin,
+ Geom::Point &pos, Geom::Point &tgt, double &len, double &rad)
+{
+ Geom::Point const iE = fin.p;
+ double const rx = fin.rx;
+ double const ry = fin.ry;
+ double const angle = fin.angle*M_PI/180.0;
+ bool const large = fin.large;
+ bool const wise = fin.clockwise;
+
+ pos = iS;
+ tgt[0] = tgt[1] = 0;
+ if (rx <= 0.0001 || ry <= 0.0001)
+ return;
+
+ double const sex = iE[0] - iS[0], sey = iE[1] - iS[1];
+ double const ca = cos (angle), sa = sin (angle);
+ double csex = ca * sex + sa * sey;
+ double csey = -sa * sex + ca * sey;
+ csex /= rx;
+ csey /= ry;
+ double l = csex * csex + csey * csey;
+ double const d = sqrt(std::max(1 - l / 4, 0.0));
+ double csdx = csey;
+ double csdy = -csex;
+ l = sqrt(l);
+ csdx /= l;
+ csdy /= l;
+ csdx *= d;
+ csdy *= d;
+
+ double sang;
+ double eang;
+ double rax = -csdx - csex / 2;
+ double ray = -csdy - csey / 2;
+ if (rax < -1)
+ {
+ sang = M_PI;
+ }
+ else if (rax > 1)
+ {
+ sang = 0;
+ }
+ else
+ {
+ sang = acos (rax);
+ if (ray < 0)
+ sang = 2 * M_PI - sang;
+ }
+ rax = -csdx + csex / 2;
+ ray = -csdy + csey / 2;
+ if (rax < -1)
+ {
+ eang = M_PI;
+ }
+ else if (rax > 1)
+ {
+ eang = 0;
+ }
+ else
+ {
+ eang = acos (rax);
+ if (ray < 0)
+ eang = 2 * M_PI - eang;
+ }
+
+ csdx *= rx;
+ csdy *= ry;
+ double drx = ca * csdx - sa * csdy;
+ double dry = sa * csdx + ca * csdy;
+
+ if (wise)
+ {
+ if (large)
+ {
+ drx = -drx;
+ dry = -dry;
+ double swap = eang;
+ eang = sang;
+ sang = swap;
+ eang += M_PI;
+ sang += M_PI;
+ if (eang >= 2 * M_PI)
+ eang -= 2 * M_PI;
+ if (sang >= 2 * M_PI)
+ sang -= 2 * M_PI;
+ }
+ }
+ else
+ {
+ if (! large)
+ {
+ drx = -drx;
+ dry = -dry;
+ double swap = eang;
+ eang = sang;
+ sang = swap;
+ eang += M_PI;
+ sang += M_PI;
+ if (eang >= 2 * M_PI)
+ eang -= 2 * M_PI;
+ if (sang >= 2 * M_PI)
+ sang -= 2 * M_PI;
+ }
+ }
+ drx += (iS[0] + iE[0]) / 2;
+ dry += (iS[1] + iE[1]) / 2;
+
+ if (wise) {
+ if (sang < eang)
+ sang += 2 * M_PI;
+ double b = sang * (1 - at) + eang * at;
+ double cb = cos (b), sb = sin (b);
+ pos[0] = drx + ca * rx * cb - sa * ry * sb;
+ pos[1] = dry + sa * rx * cb + ca * ry * sb;
+ tgt[0] = ca * rx * sb + sa * ry * cb;
+ tgt[1] = sa * rx * sb - ca * ry * cb;
+ Geom::Point dtgt;
+ dtgt[0] = -ca * rx * cb + sa * ry * sb;
+ dtgt[1] = -sa * rx * cb - ca * ry * sb;
+ len = L2(tgt);
+ rad = -len * dot(tgt, tgt) / (tgt[0] * dtgt[1] - tgt[1] * dtgt[0]);
+ tgt /= len;
+ }
+ else
+ {
+ if (sang > eang)
+ sang -= 2 * M_PI;
+ double b = sang * (1 - at) + eang * at;
+ double cb = cos (b), sb = sin (b);
+ pos[0] = drx + ca * rx * cb - sa * ry * sb;
+ pos[1] = dry + sa * rx * cb + ca * ry * sb;
+ tgt[0] = ca * rx * sb + sa * ry * cb;
+ tgt[1] = sa * rx * sb - ca * ry * cb;
+ Geom::Point dtgt;
+ dtgt[0] = -ca * rx * cb + sa * ry * sb;
+ dtgt[1] = -sa * rx * cb - ca * ry * sb;
+ len = L2(tgt);
+ rad = len * dot(tgt, tgt) / (tgt[0] * dtgt[1] - tgt[1] * dtgt[0]);
+ tgt /= len;
+ }
+
+ if (!wise) {
+ tgt = -tgt;
+ }
+}
+void
+Path::TangentOnCubAt (double at, Geom::Point const &iS, PathDescrCubicTo const &fin, bool before,
+ Geom::Point &pos, Geom::Point &tgt, double &len, double &rad)
+{
+ const Geom::Point E = fin.p;
+ const Geom::Point Sd = fin.start;
+ const Geom::Point Ed = fin.end;
+
+ pos = iS;
+ tgt = Geom::Point(0,0);
+ len = rad = 0;
+
+ const Geom::Point A = Sd + Ed - 2*E + 2*iS;
+ const Geom::Point B = 0.5*(Ed - Sd);
+ const Geom::Point C = 0.25*(6*E - 6*iS - Sd - Ed);
+ const Geom::Point D = 0.125*(4*iS + 4*E - Ed + Sd);
+ const double atb = at - 0.5;
+ pos = (atb * atb * atb)*A + (atb * atb)*B + atb*C + D;
+ const Geom::Point der = (3 * atb * atb)*A + (2 * atb)*B + C;
+ const Geom::Point dder = (6 * atb)*A + 2*B;
+ const Geom::Point ddder = 6 * A;
+
+ double l = Geom::L2 (der);
+ // lots of nasty cases. inversion points are sadly too common...
+ if (l <= 0.0001) {
+ len = 0;
+ l = L2(dder);
+ if (l <= 0.0001) {
+ l = L2(ddder);
+ if (l <= 0.0001) {
+ // pas de segment....
+ return;
+ }
+ rad = 100000000;
+ tgt = ddder / l;
+ if (before) {
+ tgt = -tgt;
+ }
+ return;
+ }
+ rad = -l * (dot(dder,dder)) / (cross(dder, ddder));
+ tgt = dder / l;
+ if (before) {
+ tgt = -tgt;
+ }
+ return;
+ }
+ len = l;
+
+ rad = -l * (dot(der,der)) / (cross(der, dder));
+
+ tgt = der / l;
+}
+
+void
+Path::TangentOnBezAt (double at, Geom::Point const &iS,
+ PathDescrIntermBezierTo & mid,
+ PathDescrBezierTo & fin, bool before, Geom::Point & pos,
+ Geom::Point & tgt, double &len, double &rad)
+{
+ pos = iS;
+ tgt = Geom::Point(0,0);
+ len = rad = 0;
+
+ const Geom::Point A = fin.p + iS - 2*mid.p;
+ const Geom::Point B = 2*mid.p - 2 * iS;
+ const Geom::Point C = iS;
+
+ pos = at * at * A + at * B + C;
+ const Geom::Point der = 2 * at * A + B;
+ const Geom::Point dder = 2 * A;
+ double l = Geom::L2(der);
+
+ if (l <= 0.0001) {
+ l = Geom::L2(dder);
+ if (l <= 0.0001) {
+ // pas de segment....
+ // Not a segment.
+ return;
+ }
+ rad = 100000000; // Why this number?
+ tgt = dder / l;
+ if (before) {
+ tgt = -tgt;
+ }
+ return;
+ }
+ len = l;
+ rad = -l * (dot(der,der)) / (cross(der, dder));
+
+ tgt = der / l;
+}
+
+void
+Path::OutlineJoin (Path * dest, Geom::Point pos, Geom::Point stNor, Geom::Point enNor, double width,
+ JoinType join, double miter, int nType)
+{
+ /*
+ Arbitrarily decide if we're on the inside or outside of a half turn.
+ A turn of 180 degrees (line path leaves the node in the same direction as it arrived)
+ is symmetric and has no real inside and outside. However when outlining we shall handle
+ one path as inside and the reverse path as outside. Handling both as inside joins (as
+ was done previously) will cut off round joins. Handling both as outside joins could
+ ideally work because both should fall together, but it seems that this causes many
+ extra nodes (due to rounding errors). Solution: for the 'half turn'-case toggle
+ inside/outside each time the same node is processed 2 consecutive times.
+ */
+ static bool TurnInside = true;
+ static Geom::Point PrevPos(0, 0);
+ TurnInside ^= PrevPos == pos;
+ PrevPos = pos;
+
+ const double angSi = cross (stNor, enNor);
+ const double angCo = dot (stNor, enNor);
+
+ if ((fabs(angSi) < .0000001) && angCo > 0) { // The join is straight -> nothing to do.
+ } else {
+ if ((angSi > 0 && width >= 0)
+ || (angSi < 0 && width < 0)) { // This is an inside join -> join is independent of chosen JoinType.
+ if ((dest->descr_cmd[dest->descr_cmd.size() - 1]->getType() == descr_lineto) && (nType == descr_lineto)) {
+ Geom::Point const biss = unit_vector(Geom::rot90( stNor - enNor ));
+ double c2 = Geom::dot (biss, enNor);
+ if (fabs(c2) > M_SQRT1_2) { // apply only to obtuse angles
+ double l = width / c2;
+ PathDescrLineTo* nLine = dynamic_cast<PathDescrLineTo*>(dest->descr_cmd[dest->descr_cmd.size() - 1]);
+ nLine->p = pos + l*biss; // relocate to bisector
+ } else {
+ dest->LineTo (pos + width*enNor);
+ }
+ } else {
+// dest->LineTo (pos); // redundant
+ dest->LineTo (pos + width*enNor);
+ }
+ } else if (angSi == 0 && TurnInside) { // Half turn (180 degrees) ... inside (see above).
+ dest->LineTo (pos + width*enNor);
+ } else { // This is an outside join -> chosen JoinType should be applied.
+ if (join == join_round) {
+ // Use the ends of the cubic: approximate the arc at the
+ // point where .., and support better the rounding of
+ // coordinates of the end points.
+
+ // utiliser des bouts de cubique: approximation de l'arc (au point ou on en est...), et supporte mieux
+ // l'arrondi des coordonnees des extremites
+ /* double angle=acos(angCo);
+ if ( angCo >= 0 ) {
+ Geom::Point stTgt,enTgt;
+ RotCCWTo(stNor,stTgt);
+ RotCCWTo(enNor,enTgt);
+ dest->CubicTo(pos.x+width*enNor.x,pos.y+width*enNor.y,
+ angle*width*stTgt.x,angle*width*stTgt.y,
+ angle*width*enTgt.x,angle*width*enTgt.y);
+ } else {
+ Geom::Point biNor;
+ Geom::Point stTgt,enTgt,biTgt;
+ biNor.x=stNor.x+enNor.x;
+ biNor.y=stNor.y+enNor.y;
+ double biL=sqrt(biNor.x*biNor.x+biNor.y*biNor.y);
+ biNor.x/=biL;
+ biNor.y/=biL;
+ RotCCWTo(stNor,stTgt);
+ RotCCWTo(enNor,enTgt);
+ RotCCWTo(biNor,biTgt);
+ dest->CubicTo(pos.x+width*biNor.x,pos.y+width*biNor.y,
+ angle*width*stTgt.x,angle*width*stTgt.y,
+ angle*width*biTgt.x,angle*width*biTgt.y);
+ dest->CubicTo(pos.x+width*enNor.x,pos.y+width*enNor.y,
+ angle*width*biTgt.x,angle*width*biTgt.y,
+ angle*width*enTgt.x,angle*width*enTgt.y);
+ }*/
+ if (width > 0) {
+ dest->ArcTo (pos + width*enNor,
+ 1.0001 * width, 1.0001 * width, 0.0, false, true);
+ } else {
+ dest->ArcTo (pos + width*enNor,
+ -1.0001 * width, -1.0001 * width, 0.0, false,
+ false);
+ }
+ } else if (join == join_pointy) {
+ Geom::Point const biss = unit_vector(Geom::rot90( stNor - enNor ));
+ double c2 = Geom::dot (biss, enNor);
+ double l = width / c2;
+ if ( fabs(l) > miter) {
+ dest->LineTo (pos + width*enNor);
+ } else {
+ if (dest->descr_cmd[dest->descr_cmd.size() - 1]->getType() == descr_lineto) {
+ PathDescrLineTo* nLine = dynamic_cast<PathDescrLineTo*>(dest->descr_cmd[dest->descr_cmd.size() - 1]);
+ nLine->p = pos+l*biss; // relocate to bisector
+ } else {
+ dest->LineTo (pos+l*biss);
+ }
+ if (nType != descr_lineto)
+ dest->LineTo (pos+width*enNor);
+ }
+ } else { // Bevel join
+ dest->LineTo (pos + width*enNor);
+ }
+ }
+ }
+}
+
+// les callbacks
+
+// see http://www.home.unix-ag.org/simon/sketch/pathstroke.py to understand what's happening here
+
+void
+Path::RecStdCubicTo (outline_callback_data * data, double tol, double width,
+ int lev)
+{
+ Geom::Point stPos, miPos, enPos;
+ Geom::Point stTgt, enTgt, miTgt, stNor, enNor, miNor;
+ double stRad, miRad, enRad;
+ double stTle, miTle, enTle;
+ // un cubic
+ {
+ PathDescrCubicTo temp(Geom::Point(data->x2, data->y2),
+ Geom::Point(data->d.c.dx1, data->d.c.dy1),
+ Geom::Point(data->d.c.dx2, data->d.c.dy2));
+
+ Geom::Point initial_point(data->x1, data->y1);
+ TangentOnCubAt (0.0, initial_point, temp, false, stPos, stTgt, stTle,
+ stRad);
+ TangentOnCubAt (0.5, initial_point, temp, false, miPos, miTgt, miTle,
+ miRad);
+ TangentOnCubAt (1.0, initial_point, temp, true, enPos, enTgt, enTle,
+ enRad);
+ stNor=stTgt.cw();
+ miNor=miTgt.cw();
+ enNor=enTgt.cw();
+ }
+
+ double stGue = 1, miGue = 1, enGue = 1;
+ // correction of the lengths of the tangent to the offset
+ // if you don't see why i wrote that, draw a little figure and everything will be clear
+ if (fabs (stRad) > 0.01)
+ stGue += width / stRad;
+ if (fabs (miRad) > 0.01)
+ miGue += width / miRad;
+ if (fabs (enRad) > 0.01)
+ enGue += width / enRad;
+ stGue *= stTle;
+ miGue *= miTle;
+ enGue *= enTle;
+
+
+ if (lev <= 0) {
+ int n_d = data->dest->CubicTo (enPos + width*enNor,
+ stGue*stTgt,
+ enGue*enTgt);
+ if (n_d >= 0) {
+ data->dest->descr_cmd[n_d]->associated = data->piece;
+ data->dest->descr_cmd[n_d]->tSt = data->tSt;
+ data->dest->descr_cmd[n_d]->tEn = data->tEn;
+ }
+ return;
+ }
+
+ Geom::Point chk;
+ const Geom::Point req = miPos + width * miNor;
+ {
+ PathDescrCubicTo temp(enPos + width * enNor,
+ stGue * stTgt,
+ enGue * enTgt);
+ double chTle, chRad;
+ Geom::Point chTgt;
+ TangentOnCubAt (0.5, stPos+width*stNor,
+ temp, false, chk, chTgt, chTle, chRad);
+ }
+ const Geom::Point diff = req - chk;
+ const double err = dot(diff,diff);
+ if (err <= tol ) { // tolerance is given as a quadratic value, no need to use tol*tol here
+// printf("%f <= %f %i\n",err,tol,lev);
+ int n_d = data->dest->CubicTo (enPos + width*enNor,
+ stGue*stTgt,
+ enGue*enTgt);
+ if (n_d >= 0) {
+ data->dest->descr_cmd[n_d]->associated = data->piece;
+ data->dest->descr_cmd[n_d]->tSt = data->tSt;
+ data->dest->descr_cmd[n_d]->tEn = data->tEn;
+ }
+ } else {
+ outline_callback_data desc = *data;
+
+ desc.tSt = data->tSt;
+ desc.tEn = (data->tSt + data->tEn) / 2;
+ desc.x1 = data->x1;
+ desc.y1 = data->y1;
+ desc.x2 = miPos[0];
+ desc.y2 = miPos[1];
+ desc.d.c.dx1 = 0.5 * stTle * stTgt[0];
+ desc.d.c.dy1 = 0.5 * stTle * stTgt[1];
+ desc.d.c.dx2 = 0.5 * miTle * miTgt[0];
+ desc.d.c.dy2 = 0.5 * miTle * miTgt[1];
+ RecStdCubicTo (&desc, tol, width, lev - 1);
+
+ desc.tSt = (data->tSt + data->tEn) / 2;
+ desc.tEn = data->tEn;
+ desc.x1 = miPos[0];
+ desc.y1 = miPos[1];
+ desc.x2 = data->x2;
+ desc.y2 = data->y2;
+ desc.d.c.dx1 = 0.5 * miTle * miTgt[0];
+ desc.d.c.dy1 = 0.5 * miTle * miTgt[1];
+ desc.d.c.dx2 = 0.5 * enTle * enTgt[0];
+ desc.d.c.dy2 = 0.5 * enTle * enTgt[1];
+ RecStdCubicTo (&desc, tol, width, lev - 1);
+ }
+}
+
+void
+Path::StdCubicTo (Path::outline_callback_data * data, double tol, double width)
+{
+// fflush (stdout);
+ RecStdCubicTo (data, tol, width, 8);
+}
+
+void
+Path::StdBezierTo (Path::outline_callback_data * data, double tol, double width)
+{
+ PathDescrBezierTo tempb(Geom::Point(data->x2, data->y2), 1);
+ PathDescrIntermBezierTo tempi(Geom::Point(data->d.b.mx, data->d.b.my));
+ Geom::Point stPos, enPos, stTgt, enTgt;
+ double stRad, enRad, stTle, enTle;
+ Geom::Point tmp(data->x1,data->y1);
+ TangentOnBezAt (0.0, tmp, tempi, tempb, false, stPos, stTgt,
+ stTle, stRad);
+ TangentOnBezAt (1.0, tmp, tempi, tempb, true, enPos, enTgt,
+ enTle, enRad);
+ data->d.c.dx1 = stTle * stTgt[0];
+ data->d.c.dy1 = stTle * stTgt[1];
+ data->d.c.dx2 = enTle * enTgt[0];
+ data->d.c.dy2 = enTle * enTgt[1];
+ RecStdCubicTo (data, tol, width, 8);
+}
+
+void
+Path::RecStdArcTo (outline_callback_data * data, double tol, double width,
+ int lev)
+{
+ Geom::Point stPos, miPos, enPos;
+ Geom::Point stTgt, enTgt, miTgt, stNor, enNor, miNor;
+ double stRad, miRad, enRad;
+ double stTle, miTle, enTle;
+ // un cubic
+ {
+ PathDescrArcTo temp(Geom::Point(data->x2, data->y2),
+ data->d.a.rx, data->d.a.ry,
+ data->d.a.angle, data->d.a.large, data->d.a.clock);
+
+ Geom::Point tmp(data->x1,data->y1);
+ TangentOnArcAt (data->d.a.stA, tmp, temp, stPos, stTgt,
+ stTle, stRad);
+ TangentOnArcAt ((data->d.a.stA + data->d.a.enA) / 2, tmp,
+ temp, miPos, miTgt, miTle, miRad);
+ TangentOnArcAt (data->d.a.enA, tmp, temp, enPos, enTgt,
+ enTle, enRad);
+ stNor=stTgt.cw();
+ miNor=miTgt.cw();
+ enNor=enTgt.cw();
+ }
+
+ double stGue = 1, miGue = 1, enGue = 1;
+ if (fabs (stRad) > 0.01)
+ stGue += width / stRad;
+ if (fabs (miRad) > 0.01)
+ miGue += width / miRad;
+ if (fabs (enRad) > 0.01)
+ enGue += width / enRad;
+ stGue *= stTle;
+ miGue *= miTle;
+ enGue *= enTle;
+ double sang, eang;
+ {
+ Geom::Point tms(data->x1,data->y1),tme(data->x2,data->y2);
+ ArcAngles (tms,tme, data->d.a.rx,
+ data->d.a.ry, data->d.a.angle*M_PI/180.0, data->d.a.large, !data->d.a.clock,
+ sang, eang);
+ }
+ double scal = eang - sang;
+ if (scal < 0)
+ scal += 2 * M_PI;
+ if (scal > 2 * M_PI)
+ scal -= 2 * M_PI;
+ scal *= data->d.a.enA - data->d.a.stA;
+
+ if (lev <= 0)
+ {
+ int n_d = data->dest->CubicTo (enPos + width*enNor,
+ stGue*scal*stTgt,
+ enGue*scal*enTgt);
+ if (n_d >= 0) {
+ data->dest->descr_cmd[n_d]->associated = data->piece;
+ data->dest->descr_cmd[n_d]->tSt = data->d.a.stA;
+ data->dest->descr_cmd[n_d]->tEn = data->d.a.enA;
+ }
+ return;
+ }
+
+ Geom::Point chk;
+ const Geom::Point req = miPos + width*miNor;
+ {
+ PathDescrCubicTo temp(enPos + width * enNor, stGue * scal * stTgt, enGue * scal * enTgt);
+ double chTle, chRad;
+ Geom::Point chTgt;
+ TangentOnCubAt (0.5, stPos+width*stNor,
+ temp, false, chk, chTgt, chTle, chRad);
+ }
+ const Geom::Point diff = req - chk;
+ const double err = (dot(diff,diff));
+ if (err <= tol) // tolerance is given as a quadratic value, no need to use tol*tol here
+ {
+ int n_d = data->dest->CubicTo (enPos + width*enNor,
+ stGue*scal*stTgt,
+ enGue*scal*enTgt);
+ if (n_d >= 0) {
+ data->dest->descr_cmd[n_d]->associated = data->piece;
+ data->dest->descr_cmd[n_d]->tSt = data->d.a.stA;
+ data->dest->descr_cmd[n_d]->tEn = data->d.a.enA;
+ }
+ } else {
+ outline_callback_data desc = *data;
+
+ desc.d.a.stA = data->d.a.stA;
+ desc.d.a.enA = (data->d.a.stA + data->d.a.enA) / 2;
+ RecStdArcTo (&desc, tol, width, lev - 1);
+
+ desc.d.a.stA = (data->d.a.stA + data->d.a.enA) / 2;
+ desc.d.a.enA = data->d.a.enA;
+ RecStdArcTo (&desc, tol, width, lev - 1);
+ }
+}
+
+void
+Path::StdArcTo (Path::outline_callback_data * data, double tol, double width)
+{
+ data->d.a.stA = 0.0;
+ data->d.a.enA = 1.0;
+ RecStdArcTo (data, tol, width, 8);
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/livarot/PathSimplify.cpp b/src/livarot/PathSimplify.cpp
new file mode 100644
index 0000000..4317783
--- /dev/null
+++ b/src/livarot/PathSimplify.cpp
@@ -0,0 +1,1550 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * TODO: insert short description here
+ *//*
+ * Authors:
+ * see git history
+ * Fred
+ *
+ * Copyright (C) 2018 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <memory>
+#include <glib.h>
+#include <2geom/affine.h>
+#include "livarot/Path.h"
+#include "livarot/path-description.h"
+
+/*
+ * Reassembling polyline segments into cubic bezier patches
+ * thes functions do not need the back data. but they are slower than recomposing
+ * path descriptions when you have said back data (it's always easier with a model)
+ * there's a bezier fitter in bezier-utils.cpp too. the main difference is the way bezier patch are split
+ * here: walk on the polyline, trying to extend the portion you can fit by respecting the treshhold, split when
+ * treshhold is exceeded. when encountering a "forced" point, lower the treshhold to favor splitting at that point
+ * in bezier-utils: fit the whole polyline, get the position with the higher deviation to the fitted curve, split
+ * there and recurse
+ */
+
+
+// algo d'origine: http://www.cs.mtu.edu/~shene/COURSES/cs3621/NOTES/INT-APP/CURVE-APP-global.html
+
+// need the b-spline basis for cubic splines
+// pas oublier que c'est une b-spline clampee
+// et que ca correspond a une courbe de bezier normale
+#define N03(t) ((1.0-t)*(1.0-t)*(1.0-t))
+#define N13(t) (3*t*(1.0-t)*(1.0-t))
+#define N23(t) (3*t*t*(1.0-t))
+#define N33(t) (t*t*t)
+// quadratic b-splines (jsut in case)
+#define N02(t) ((1.0-t)*(1.0-t))
+#define N12(t) (2*t*(1.0-t))
+#define N22(t) (t*t)
+// linear interpolation b-splines
+#define N01(t) ((1.0-t))
+#define N11(t) (t)
+
+
+
+void Path::Simplify(double treshhold)
+{
+ // There is nothing to fit if you have 0 to 1 points
+ if (pts.size() <= 1) {
+ return;
+ }
+
+ // clear all existing path descriptions
+ Reset();
+
+ // each path (where a path is a MoveTo followed by one or more LineTo) is fitted on separately
+ // Say you had M L L L L M L L L M L L L L
+ // pattern -------- ------- ---------
+ // path 1 path 2 path 3
+ // Each would be simplified individually.
+
+ int lastM = 0; // index of the lastMove
+ while (lastM < int(pts.size())) {
+ int lastP = lastM + 1; // last point
+ while (lastP < int(pts.size())
+ && (pts[lastP].isMoveTo == polyline_lineto
+ || pts[lastP].isMoveTo == polyline_forced)) // if it's a LineTo or a forcedPoint we move forward
+ {
+ lastP++;
+ }
+ // we would only come out of the above while loop when pts[lastP] becomes a MoveTo or we
+ // run out of points
+ // M L L L L L
+ // 0 1 2 3 4 5 6 <-- we came out from loop here
+ // lastM = 0; lastP = 6; lastP - lastM = 6;
+ // We pass the first point the algorithm should start at (lastM) and the total number of
+ // points (lastP - lastM)
+ DoSimplify(lastM, lastP - lastM, treshhold);
+
+ lastM = lastP;
+ }
+}
+
+
+#if 0
+// dichomtomic method to get distance to curve approximation
+// a real polynomial solver would get the minimum more efficiently, but since the polynom
+// would likely be of degree >= 5, that would imply using some generic solver, liek using the sturm method
+static double RecDistanceToCubic(Geom::Point const &iS, Geom::Point const &isD,
+ Geom::Point const &iE, Geom::Point const &ieD,
+ Geom::Point &pt, double current, int lev, double st, double et)
+{
+ if ( lev <= 0 ) {
+ return current;
+ }
+
+ Geom::Point const m = 0.5 * (iS + iE) + 0.125 * (isD - ieD);
+ Geom::Point const md = 0.75 * (iE - iS) - 0.125 * (isD + ieD);
+ double const mt = (st + et) / 2;
+
+ Geom::Point const hisD = 0.5 * isD;
+ Geom::Point const hieD = 0.5 * ieD;
+
+ Geom::Point const mp = pt - m;
+ double nle = Geom::dot(mp, mp);
+
+ if ( nle < current ) {
+
+ current = nle;
+ nle = RecDistanceToCubic(iS, hisD, m, md, pt, current, lev - 1, st, mt);
+ if ( nle < current ) {
+ current = nle;
+ }
+ nle = RecDistanceToCubic(m, md, iE, hieD, pt, current, lev - 1, mt, et);
+ if ( nle < current ) {
+ current = nle;
+ }
+
+ } else if ( nle < 2 * current ) {
+
+ nle = RecDistanceToCubic(iS, hisD, m, md, pt, current, lev - 1, st, mt);
+ if ( nle < current ) {
+ current = nle;
+ }
+ nle = RecDistanceToCubic(m, md, iE, hieD, pt, current, lev - 1, mt, et);
+ if ( nle < current ) {
+ current = nle;
+ }
+ }
+
+ return current;
+}
+#endif
+
+/**
+ * Smallest distance from a point to a line.
+ *
+ * You might think this function calculates the distance from a CubicBezier to a point? Neh, it
+ * doesn't do that. Firstly, this function doesn't care at all about a CubicBezier, as you can see
+ * res.start and res.end are not even used in the function body. It calculates the shortest
+ * possible distance to get from the point pt to the line from start to res.p.
+ * There two possibilities so let me illustrate:
+ * Case 1:
+ *
+ * o (pt)
+ * |
+ * | <------ returns this distance
+ * |
+ * o-------------------------o
+ * start res.p
+ *
+ * Case 2:
+ *
+ * o (pt)
+ * -
+ * -
+ * - <--- returns this distance
+ * -
+ * -
+ * o-------------------------o
+ * start res.p
+ * if the line is defined by l(t) over 0 <= t <= 1, this distance between pt and any point on line
+ * l(t) is defined as |l(t) - pt|. This function returns the minimum of this function over t.
+ *
+ * @param start The start point of the cubic Bezier.
+ * @param res The cubic Bezier command which we really don't care about. We only use res.p.
+ * @param pt The point to measure the distance from.
+ *
+ * @return See the comments above.
+ */
+static double DistanceToCubic(Geom::Point const &start, PathDescrCubicTo res, Geom::Point &pt)
+{
+ Geom::Point const sp = pt - start;
+ Geom::Point const ep = pt - res.p;
+ double nle = Geom::dot(sp, sp);
+ double nnle = Geom::dot(ep, ep);
+ if ( nnle < nle ) {
+ nle = nnle;
+ }
+
+ Geom::Point seg = res.p - start;
+ nnle = Geom::cross(sp, seg);
+ nnle *= nnle;
+ nnle /= Geom::dot(seg, seg);
+ if ( nnle < nle ) {
+ if ( Geom::dot(sp,seg) >= 0 ) {
+ seg = start - res.p;
+ if ( Geom::dot(ep,seg) >= 0 ) {
+ nle = nnle;
+ }
+ }
+ }
+
+ return nle;
+}
+
+
+/**
+ * Simplification on a subpath.
+ */
+
+void Path::DoSimplify(int off, int N, double treshhold)
+{
+ // non-dichotomic method: grow an interval of points approximated by a curve, until you reach the treshhold, and repeat
+ // nothing to do with one/zero point(s).
+ if (N <= 1) {
+ return;
+ }
+
+ int curP = 0;
+
+ fitting_tables data;
+ data.Xk = data.Yk = data.Qk = nullptr;
+ data.tk = data.lk = nullptr;
+ data.fk = nullptr;
+ data.totLen = 0;
+ data.nbPt = data.maxPt = data.inPt = 0;
+
+ // MoveTo to the first point
+ Geom::Point const moveToPt = pts[off].p;
+ MoveTo(moveToPt);
+ // endToPt stores the last point of each cubic bezier patch (or line segment) that we add
+ Geom::Point endToPt = moveToPt;
+
+ // curP is a local index and we add the offset (off) in it to get the real
+ // index. The loop has N - 1 instead of N because there is no point starting
+ // the fitting process on the last point
+ while (curP < N - 1) {
+ // lastP becomes the lastPoint to fit on, basically, we wanna try fitting
+ // on a sequence of points that start with curP and ends at lastP
+ // We start with curP being 0 (the first point) and lastP being 1 (the second point)
+ // M holds the total number of points we are trying to fix
+ int lastP = curP + 1;
+ int M = 2;
+
+ // remettre a zero
+ data.inPt = data.nbPt = 0;
+
+ // a cubic bezier command that will be the patch fitted, which will be appended to the list
+ // of path commands
+ PathDescrCubicTo res(Geom::Point(0, 0), Geom::Point(0, 0), Geom::Point(0, 0));
+ // a flag to indicate if there is a forced point in the current sequence that
+ // we are trying to fit
+ bool contains_forced = false;
+ // the fitting code here is called in a binary search fashion, you can say
+ // that we have fixed the start point for our fitting sequence (curP) and
+ // need the highest possible endpoint (lastP) (highest in index) such
+ // that the threshold is respected.
+ // To do this search, we add "step" number of points to the current sequence
+ // and fit, if successful, we add "step" more points, if not, we go back to
+ // the last sequence of points, divide step by 2 and try extending again, this
+ // process repeats until step becomes 0.
+ // One more point to mention is how forced points are handled. Once lastP is
+ // at a forced point, threshold will not be happy with any point after lastP, thus,
+ // step will slowly reduce to 0, ultimately finishing the patch at the forced point.
+ int step = 64;
+ while ( step > 0 ) {
+ int forced_pt = -1;
+ int worstP = -1;
+ // this loop attempts to fit and if the threshold was fine with our fit, we
+ // add "step" more points to the sequence that we are fitting on
+ do {
+ // if the point if forced, we set the flag, basically if there is a forced
+ // point in the sequence (anywhere), this code will trigger at some point (I think)
+ if (pts[off + lastP].isMoveTo == polyline_forced) {
+ contains_forced = true;
+ }
+ forced_pt = lastP; // store the forced point (any regular point also gets stored :/)
+ lastP += step; // move the end marker by + step
+ M += step; // add "step" to number of points we are trying to fit
+ // the loop breaks if we either ran out of boundaries or the threshold didn't like
+ // the fit
+ } while (lastP < N && ExtendFit(off + curP, M, data,
+ (contains_forced) ? 0.05 * treshhold : treshhold, // <-- if the last point here is a forced one we
+ res, worstP) ); // make the threshold really strict so it'll definitely complain about the fit thus
+ // favoring us to stop and go back by "step" units
+ // did we go out of boundaries?
+ if (lastP >= N) {
+ lastP -= step; // okay, come back by "step" units
+ M -= step;
+ } else { // the threshold complained
+ // le dernier a echoue
+ lastP -= step; // come back by "step" units
+ M -= step;
+
+ if ( contains_forced ) {
+ lastP = forced_pt; // we ensure that we are back at "forced" point.
+ M = lastP - curP + 1;
+ }
+
+ // fit stuff again (so we save the results in res); Threshold shouldn't complain
+ // with this btw
+ AttemptSimplify(off + curP, M, treshhold, res, worstP); // ca passe forcement
+ }
+ step /= 2; // divide step by 2
+ }
+
+ // mark lastP as the end point of the sequence we are fitting on
+ endToPt = pts[off + lastP].p;
+ // add a patch, for two points a line else a cubic bezier, res has already been calculated
+ // by AttemptSimplify
+ if (M <= 2) {
+ LineTo(endToPt);
+ } else {
+ CubicTo(endToPt, res.start, res.end);
+ }
+ // next patch starts where this one ended
+ curP = lastP;
+ }
+
+ // if the last point that we added is very very close to the first one, it's a loop so close
+ // it.
+ if (Geom::LInfty(endToPt - moveToPt) < 0.00001) {
+ Close();
+ }
+
+ g_free(data.Xk);
+ g_free(data.Yk);
+ g_free(data.Qk);
+ g_free(data.tk);
+ g_free(data.lk);
+ g_free(data.fk);
+}
+
+
+// warning: slow
+// idea behind this feature: splotches appear when trying to fit a small number of points: you can
+// get a cubic bezier that fits the points very well but doesn't fit the polyline itself
+// so we add a bit of the error at the middle of each segment of the polyline
+// also we restrict this to <=20 points, to avoid unnecessary computations
+#define with_splotch_killer
+
+// primitive= calc the cubic bezier patche that fits Xk and Yk best
+// Qk est deja alloue
+// retourne false si probleme (matrice non-inversible)
+bool Path::FitCubic(Geom::Point const &start, PathDescrCubicTo &res,
+ double *Xk, double *Yk, double *Qk, double *tk, int nbPt)
+{
+ Geom::Point const end = res.p;
+
+ // la matrice tNN
+ Geom::Affine M(0, 0, 0, 0, 0, 0);
+ for (int i = 1; i < nbPt - 1; i++) {
+ M[0] += N13(tk[i]) * N13(tk[i]);
+ M[1] += N23(tk[i]) * N13(tk[i]);
+ M[2] += N13(tk[i]) * N23(tk[i]);
+ M[3] += N23(tk[i]) * N23(tk[i]);
+ }
+
+ double const det = M.det();
+ if (fabs(det) < 0.000001) {
+ res.start[0]=res.start[1]=0.0;
+ res.end[0]=res.end[1]=0.0;
+ return false;
+ }
+
+ Geom::Affine const iM = M.inverse();
+ M = iM;
+
+ // phase 1: abcisses
+ // calcul des Qk
+ Xk[0] = start[0];
+ Yk[0] = start[1];
+ Xk[nbPt - 1] = end[0];
+ Yk[nbPt - 1] = end[1];
+
+ for (int i = 1; i < nbPt - 1; i++) {
+ Qk[i] = Xk[i] - N03 (tk[i]) * Xk[0] - N33 (tk[i]) * Xk[nbPt - 1];
+ }
+
+ // le vecteur Q
+ Geom::Point Q(0, 0);
+ for (int i = 1; i < nbPt - 1; i++) {
+ Q[0] += N13 (tk[i]) * Qk[i];
+ Q[1] += N23 (tk[i]) * Qk[i];
+ }
+
+ Geom::Point P = Q * M;
+ Geom::Point cp1;
+ Geom::Point cp2;
+ cp1[Geom::X] = P[Geom::X];
+ cp2[Geom::X] = P[Geom::Y];
+
+ // phase 2: les ordonnees
+ for (int i = 1; i < nbPt - 1; i++) {
+ Qk[i] = Yk[i] - N03 (tk[i]) * Yk[0] - N33 (tk[i]) * Yk[nbPt - 1];
+ }
+
+ // le vecteur Q
+ Q = Geom::Point(0, 0);
+ for (int i = 1; i < nbPt - 1; i++) {
+ Q[0] += N13 (tk[i]) * Qk[i];
+ Q[1] += N23 (tk[i]) * Qk[i];
+ }
+
+ P = Q * M;
+ cp1[Geom::Y] = P[Geom::X];
+ cp2[Geom::Y] = P[Geom::Y];
+
+ res.start = 3.0 * (cp1 - start);
+ res.end = 3.0 * (end - cp2 );
+
+ return true;
+}
+
+
+bool Path::ExtendFit(int off, int N, fitting_tables &data, double treshhold, PathDescrCubicTo &res, int &worstP)
+{
+ // if N is greater or equal to data.maxPt, reallocate total of 2*N+1 and copy existing data to
+ // those arrays
+ if ( N >= data.maxPt ) {
+ data.maxPt = 2 * N + 1;
+ data.Xk = (double *) g_realloc(data.Xk, data.maxPt * sizeof(double));
+ data.Yk = (double *) g_realloc(data.Yk, data.maxPt * sizeof(double));
+ data.Qk = (double *) g_realloc(data.Qk, data.maxPt * sizeof(double));
+ data.tk = (double *) g_realloc(data.tk, data.maxPt * sizeof(double));
+ data.lk = (double *) g_realloc(data.lk, data.maxPt * sizeof(double));
+ data.fk = (char *) g_realloc(data.fk, data.maxPt * sizeof(char));
+ }
+
+ // is N greater than data.inPt? data.inPt seems to hold the total number of points stored in X,
+ // Y, fk of data and thus, we add those that are not there but now should be.
+ if ( N > data.inPt ) {
+ for (int i = data.inPt; i < N; i++) {
+ data.Xk[i] = pts[off + i].p[Geom::X];
+ data.Yk[i] = pts[off + i].p[Geom::Y];
+ data.fk[i] = ( pts[off + i].isMoveTo == polyline_forced ) ? 0x01 : 0x00;
+ }
+ data.lk[0] = 0;
+ data.tk[0] = 0;
+
+ // calculate the total length of the points that already existed in data
+ double prevLen = 0;
+ for (int i = 0; i < data.inPt; i++) {
+ prevLen += data.lk[i];
+ }
+ data.totLen = prevLen;
+
+ // calculate the lengths data.lk for the new points that we just added
+ for (int i = ( (data.inPt > 0) ? data.inPt : 1); i < N; i++) {
+ Geom::Point diff;
+ diff[Geom::X] = data.Xk[i] - data.Xk[i - 1];
+ diff[Geom::Y] = data.Yk[i] - data.Yk[i - 1];
+ data.lk[i] = Geom::L2(diff);
+ data.totLen += data.lk[i];
+ data.tk[i] = data.totLen; // set tk[i] to the length so far...
+ }
+
+ // for the points that existed already, multiply by their previous total length to convert
+ // the time values into the actual "length so far".
+ for (int i = 0; i < data.inPt; i++) {
+ data.tk[i] *= prevLen;
+ data.tk[i] /= data.totLen; // divide by the new total length to get the newer t value
+ }
+
+ for (int i = data.inPt; i < N; i++) { // now divide for the newer ones too
+ data.tk[i] /= data.totLen;
+ }
+ data.inPt = N; // update inPt to include the new points too now
+ }
+
+ // this block is for the situation where you just did a fitting on say 0 to 20 points and now
+ // you are doing one on 0 to 15 points. N was previously 20 and now it's 15. While your lk
+ // values are still fine, your tk values are messed up since the tk is 1 at point 20 when
+ // it should be 1 at point 15 now. So we recalculate tk values.
+ if ( N < data.nbPt ) {
+ // We've gone too far; we'll have to recalulate the .tk.
+ data.totLen = 0;
+ data.tk[0] = 0;
+ data.lk[0] = 0;
+ for (int i = 1; i < N; i++) {
+ data.totLen += data.lk[i];
+ data.tk[i] = data.totLen;
+ }
+
+ for (int i = 1; i < N; i++) {
+ data.tk[i] /= data.totLen;
+ }
+ }
+
+ data.nbPt = N;
+
+ /*
+ * There is something that I think is wrong with this implementation. Say we initially fit on
+ * point 0 to 10, it works well, so we add 10 more points and fit on 0-20 points. Out tk values
+ * are correct so far. That fit is problematic so maybe we fit on 0-15 but now, N < data.nbPt
+ * block will run and recalculate tk values. So we will have correct tk values from 0-15 but
+ * the older tk values in 15-20. Say after this we fit on 0-18 points. Well we ran into a
+ * problem N > data.nbPt so no recalculation happens. data.inPt is already 20 so nothing
+ * happens in that block either. We have invalid tk values and FitCubic will be called with
+ * these invalid values. I've drawn paths and seen this situation where FitCubic was called
+ * with wrong tk values. Values that would go 0, 0.25, 0.5, 0.75, 1, 0.80, 0.90. Maybe the
+ * consequences are not that terrible so we didn't notice this in results?
+ * TODO: Do we fix this or investigate more?
+ */
+
+ if ( data.nbPt <= 0 ) {
+ return false;
+ }
+
+ res.p[0] = data.Xk[data.nbPt - 1];
+ res.p[1] = data.Yk[data.nbPt - 1];
+ res.start[0] = res.start[1] = 0;
+ res.end[0] = res.end[1] = 0;
+ worstP = 1;
+ if ( N <= 2 ) {
+ return true;
+ }
+ // this is the same popular block that's found in the other AttemptSimplify so please go there
+ // to see what it does and how
+ if ( data.totLen < 0.0001 ) {
+ double worstD = 0;
+ Geom::Point start;
+ worstP = -1;
+ start[0] = data.Xk[0];
+ start[1] = data.Yk[0];
+ for (int i = 1; i < N; i++) {
+ Geom::Point nPt;
+ bool isForced = data.fk[i];
+ nPt[0] = data.Xk[i];
+ nPt[1] = data.Yk[i];
+
+ double nle = DistanceToCubic(start, res, nPt);
+ if ( isForced ) {
+ // forced points are favored for splitting the recursion; we do this by increasing their distance
+ if ( worstP < 0 || 2*nle > worstD ) {
+ worstP = i;
+ worstD = 2*nle;
+ }
+ } else {
+ if ( worstP < 0 || nle > worstD ) {
+ worstP = i;
+ worstD = nle;
+ }
+ }
+ }
+ return true; // it's weird that this is the only block of this kind that returns true instead of false. Livarot
+ // can you please be more consistent? -_-
+ }
+
+ return AttemptSimplify(data, treshhold, res, worstP);
+}
+
+
+// fit a polyline to a bezier patch, return true is treshhold not exceeded (ie: you can continue)
+// version that uses tables from the previous iteration, to minimize amount of work done
+bool Path::AttemptSimplify (fitting_tables &data,double treshhold, PathDescrCubicTo & res,int &worstP)
+{
+ Geom::Point start,end;
+ // pour une coordonnee
+ Geom::Point cp1, cp2;
+
+ worstP = 1;
+ if (pts.size() == 2) {
+ return true;
+ }
+
+ start[0] = data.Xk[0];
+ start[1] = data.Yk[0];
+ cp1[0] = data.Xk[1];
+ cp1[1] = data.Yk[1];
+ end[0] = data.Xk[data.nbPt - 1];
+ end[1] = data.Yk[data.nbPt - 1];
+ cp2 = cp1;
+
+ if (pts.size() == 3) {
+ // start -> cp1 -> end
+ res.start = cp1 - start;
+ res.end = end - cp1;
+ worstP = 1;
+ return true;
+ }
+
+ if ( FitCubic(start, res, data.Xk, data.Yk, data.Qk, data.tk, data.nbPt) ) {
+ cp1 = start + res.start / 3;
+ cp2 = end - res.end / 3;
+ } else {
+ // aie, non-inversible
+ double worstD = 0;
+ worstP = -1;
+ for (int i = 1; i < data.nbPt; i++) {
+ Geom::Point nPt;
+ nPt[Geom::X] = data.Xk[i];
+ nPt[Geom::Y] = data.Yk[i];
+ double nle = DistanceToCubic(start, res, nPt);
+ if ( data.fk[i] ) {
+ // forced points are favored for splitting the recursion; we do this by increasing their distance
+ if ( worstP < 0 || 2 * nle > worstD ) {
+ worstP = i;
+ worstD = 2 * nle;
+ }
+ } else {
+ if ( worstP < 0 || nle > worstD ) {
+ worstP = i;
+ worstD = nle;
+ }
+ }
+ }
+ return false;
+ }
+
+ // calcul du delta= pondere par les longueurs des segments
+ double delta = 0;
+ {
+ double worstD = 0;
+ worstP = -1;
+ Geom::Point prevAppP;
+ Geom::Point prevP;
+ double prevDist;
+ prevP[Geom::X] = data.Xk[0];
+ prevP[Geom::Y] = data.Yk[0];
+ prevAppP = prevP; // le premier seulement
+ prevDist = 0;
+#ifdef with_splotch_killer
+ if ( data.nbPt <= 20 ) {
+ for (int i = 1; i < data.nbPt - 1; i++) {
+ Geom::Point curAppP;
+ Geom::Point curP;
+ double curDist;
+ Geom::Point midAppP;
+ Geom::Point midP;
+ double midDist;
+
+ curAppP[Geom::X] = N13(data.tk[i]) * cp1[Geom::X] +
+ N23(data.tk[i]) * cp2[Geom::X] +
+ N03(data.tk[i]) * data.Xk[0] +
+ N33(data.tk[i]) * data.Xk[data.nbPt - 1];
+
+ curAppP[Geom::Y] = N13(data.tk[i]) * cp1[Geom::Y] +
+ N23(data.tk[i]) * cp2[Geom::Y] +
+ N03(data.tk[i]) * data.Yk[0] +
+ N33(data.tk[i]) * data.Yk[data.nbPt - 1];
+
+ curP[Geom::X] = data.Xk[i];
+ curP[Geom::Y] = data.Yk[i];
+ double mtk = 0.5 * (data.tk[i] + data.tk[i - 1]);
+
+ midAppP[Geom::X] = N13(mtk) * cp1[Geom::X] +
+ N23(mtk) * cp2[Geom::X] +
+ N03(mtk) * data.Xk[0] +
+ N33(mtk) * data.Xk[data.nbPt - 1];
+
+ midAppP[Geom::Y] = N13(mtk) * cp1[Geom::Y] +
+ N23(mtk) * cp2[Geom::Y] +
+ N03(mtk) * data.Yk[0] +
+ N33(mtk) * data.Yk[data.nbPt - 1];
+
+ midP = 0.5 * (curP + prevP);
+
+ Geom::Point diff = curAppP - curP;
+ curDist = dot(diff, diff);
+ diff = midAppP - midP;
+ midDist = dot(diff, diff);
+
+ delta += 0.3333 * (curDist + prevDist + midDist) * data.lk[i];
+ if ( curDist > worstD ) {
+ worstD = curDist;
+ worstP = i;
+ } else if ( data.fk[i] && 2 * curDist > worstD ) {
+ worstD = 2*curDist;
+ worstP = i;
+ }
+ prevP = curP;
+ prevAppP = curAppP;
+ prevDist = curDist;
+ }
+ delta /= data.totLen;
+
+ } else {
+#endif
+ for (int i = 1; i < data.nbPt - 1; i++) {
+ Geom::Point curAppP;
+ Geom::Point curP;
+ double curDist;
+
+ curAppP[Geom::X] = N13(data.tk[i]) * cp1[Geom::X] +
+ N23(data.tk[i]) * cp2[Geom::X] +
+ N03(data.tk[i]) * data.Xk[0] +
+ N33(data.tk[i]) * data.Xk[data.nbPt - 1];
+
+ curAppP[Geom::Y] = N13(data.tk[i]) * cp1[Geom::Y] +
+ N23(data.tk[i]) * cp2[Geom::Y] +
+ N03(data.tk[i]) * data.Yk[0] +
+ N33(data.tk[i]) * data.Yk[data.nbPt - 1];
+
+ curP[Geom::X] = data.Xk[i];
+ curP[Geom::Y] = data.Yk[i];
+
+ Geom::Point diff = curAppP-curP;
+ curDist = dot(diff, diff);
+ delta += curDist;
+
+ if ( curDist > worstD ) {
+ worstD = curDist;
+ worstP = i;
+ } else if ( data.fk[i] && 2 * curDist > worstD ) {
+ worstD = 2*curDist;
+ worstP = i;
+ }
+ prevP = curP;
+ prevAppP = curAppP;
+ prevDist = curDist;
+ }
+#ifdef with_splotch_killer
+ }
+#endif
+ }
+
+ if (delta < treshhold * treshhold) {
+ // premier jet
+
+ // Refine a little.
+ for (int i = 1; i < data.nbPt - 1; i++) {
+ Geom::Point pt(data.Xk[i], data.Yk[i]);
+ data.tk[i] = RaffineTk(pt, start, cp1, cp2, end, data.tk[i]);
+ if (data.tk[i] < data.tk[i - 1]) {
+ // Force tk to be monotonic non-decreasing.
+ data.tk[i] = data.tk[i - 1];
+ }
+ }
+
+ if ( FitCubic(start, res, data.Xk, data.Yk, data.Qk, data.tk, data.nbPt) == false) {
+ // ca devrait jamais arriver, mais bon
+ res.start = 3.0 * (cp1 - start);
+ res.end = 3.0 * (end - cp2 );
+ return true;
+ }
+
+ double ndelta = 0;
+ {
+ double worstD = 0;
+ worstP = -1;
+ Geom::Point prevAppP;
+ Geom::Point prevP(data.Xk[0], data.Yk[0]);
+ double prevDist = 0;
+ prevAppP = prevP; // le premier seulement
+#ifdef with_splotch_killer
+ if ( data.nbPt <= 20 ) {
+ for (int i = 1; i < data.nbPt - 1; i++) {
+ Geom::Point curAppP;
+ Geom::Point curP;
+ double curDist;
+ Geom::Point midAppP;
+ Geom::Point midP;
+ double midDist;
+
+ curAppP[Geom::X] = N13(data.tk[i]) * cp1[Geom::X] +
+ N23(data.tk[i]) * cp2[Geom::X] +
+ N03(data.tk[i]) * data.Xk[0] +
+ N33(data.tk[i]) * data.Xk[data.nbPt - 1];
+
+ curAppP[Geom::Y] = N13(data.tk[i]) * cp1[Geom::Y] +
+ N23(data.tk[i]) * cp2[Geom::Y] +
+ N03(data.tk[i]) * data.Yk[0] +
+ N33(data.tk[i]) * data.Yk[data.nbPt - 1];
+
+ curP[Geom::X] = data.Xk[i];
+ curP[Geom::Y] = data.Yk[i];
+ double mtk = 0.5 * (data.tk[i] + data.tk[i - 1]);
+
+ midAppP[Geom::X] = N13(mtk) * cp1[Geom::X] +
+ N23(mtk) * cp2[Geom::X] +
+ N03(mtk) * data.Xk[0] +
+ N33(mtk) * data.Xk[data.nbPt - 1];
+
+ midAppP[Geom::Y] = N13(mtk) * cp1[Geom::Y] +
+ N23(mtk) * cp2[Geom::Y] +
+ N03(mtk) * data.Yk[0] +
+ N33(mtk) * data.Yk[data.nbPt - 1];
+
+ midP = 0.5 * (curP + prevP);
+
+ Geom::Point diff = curAppP - curP;
+ curDist = dot(diff, diff);
+
+ diff = midAppP - midP;
+ midDist = dot(diff, diff);
+
+ ndelta += 0.3333 * (curDist + prevDist + midDist) * data.lk[i];
+
+ if ( curDist > worstD ) {
+ worstD = curDist;
+ worstP = i;
+ } else if ( data.fk[i] && 2 * curDist > worstD ) {
+ worstD = 2*curDist;
+ worstP = i;
+ }
+
+ prevP = curP;
+ prevAppP = curAppP;
+ prevDist = curDist;
+ }
+ ndelta /= data.totLen;
+ } else {
+#endif
+ for (int i = 1; i < data.nbPt - 1; i++) {
+ Geom::Point curAppP;
+ Geom::Point curP;
+ double curDist;
+
+ curAppP[Geom::X] = N13(data.tk[i]) * cp1[Geom::X] +
+ N23(data.tk[i]) * cp2[Geom::X] +
+ N03(data.tk[i]) * data.Xk[0] +
+ N33(data.tk[i]) * data.Xk[data.nbPt - 1];
+
+ curAppP[Geom::Y] = N13(data.tk[i]) * cp1[Geom::Y] +
+ N23(data.tk[i]) * cp2[1] +
+ N03(data.tk[i]) * data.Yk[0] +
+ N33(data.tk[i]) * data.Yk[data.nbPt - 1];
+
+ curP[Geom::X] = data.Xk[i];
+ curP[Geom::Y] = data.Yk[i];
+
+ Geom::Point diff = curAppP - curP;
+ curDist = dot(diff, diff);
+
+ ndelta += curDist;
+
+ if ( curDist > worstD ) {
+ worstD = curDist;
+ worstP = i;
+ } else if ( data.fk[i] && 2 * curDist > worstD ) {
+ worstD = 2 * curDist;
+ worstP = i;
+ }
+ prevP = curP;
+ prevAppP = curAppP;
+ prevDist = curDist;
+ }
+#ifdef with_splotch_killer
+ }
+#endif
+ }
+
+ if (ndelta < delta + 0.00001) {
+ return true;
+ } else {
+ // nothing better to do
+ res.start = 3.0 * (cp1 - start);
+ res.end = 3.0 * (end - cp2 );
+ }
+
+ return true;
+ }
+
+ return false;
+}
+
+
+bool Path::AttemptSimplify(int off, int N, double treshhold, PathDescrCubicTo &res,int &worstP)
+{
+ Geom::Point start;
+ Geom::Point end;
+
+ // pour une coordonnee
+ double *Xk; // la coordonnee traitee (x puis y)
+ double *Yk; // la coordonnee traitee (x puis y)
+ double *lk; // les longueurs de chaque segment
+ double *tk; // les tk
+ double *Qk; // les Qk
+ char *fk; // si point force
+
+ Geom::Point cp1; // first control point of the cubic patch
+ Geom::Point cp2; // second control point of the cubic patch
+
+ // If only two points, return immediately, but why say that point 1 has worst error though?
+ if (N == 2) {
+ worstP = 1;
+ return true;
+ }
+
+ start = pts[off].p; // point at index "off" is start
+ cp1 = pts[off + 1].p; // for now take the first control point to be the point after "off"
+ end = pts[off + N - 1].p; // last point would be end of the patch of course
+
+ // we will return the control handles in "res" right? so set the end point but set the handles
+ // to zero for now
+ res.p = end;
+ res.start[0] = res.start[1] = 0;
+ res.end[0] = res.end[1] = 0;
+
+ // if there are 3 points only, we fit a cubic patch that starts at the first point, ends at the
+ // third point and has both control points on the 2nd point
+ if (N == 3) {
+ // start -> cp1 -> end
+ res.start = cp1 - start;
+ res.end = end - cp1;
+ worstP = 1;
+ return true;
+ }
+
+ // Totally inefficient, allocates & deallocates all the time.
+ // allocate arrows for fitting information
+ tk = (double *) g_malloc(N * sizeof(double));
+ Qk = (double *) g_malloc(N * sizeof(double));
+ Xk = (double *) g_malloc(N * sizeof(double));
+ Yk = (double *) g_malloc(N * sizeof(double));
+ lk = (double *) g_malloc(N * sizeof(double));
+ fk = (char *) g_malloc(N * sizeof(char));
+
+ // chord length method
+ tk[0] = 0.0;
+ lk[0] = 0.0;
+ {
+ // Not setting Xk[0], Yk[0] might look like a bug, but FitCubic sets it later before
+ // it performs the fit
+ Geom::Point prevP = start; // store the first point
+ for (int i = 1; i < N; i++) { // start the loop on the second point
+ Xk[i] = pts[off + i].p[Geom::X]; // store the x coordinate
+ Yk[i] = pts[off + i].p[Geom::Y]; // store the y coordinate
+
+ // mark the point as forced if it's forced
+ if ( pts[off + i].isMoveTo == polyline_forced ) {
+ fk[i] = 0x01;
+ } else {
+ fk[i] = 0;
+ }
+
+ // calculate a vector from previous point to this point and store its length in lk
+ Geom::Point diff(Xk[i] - prevP[Geom::X], Yk[i] - prevP[1]);
+ prevP[0] = Xk[i]; // set prev to current point for next iteration
+ prevP[1] = Yk[i]; // set prev to current point for next iteration
+ lk[i] = Geom::L2(diff);
+ tk[i] = tk[i - 1] + lk[i]; // tk should have the length till this point, later we divide whole thing by total length to calculate time
+ }
+ }
+
+ // if total length is below 0.00001 (very unrealistic scenario)
+ if (tk[N - 1] < 0.00001) {
+ // longueur nulle
+ res.start[0] = res.start[1] = 0; // set start handle to zero
+ res.end[0] = res.end[1] = 0; // set end handle to zero
+ double worstD = 0; // worst distance b/w the curve and the polyline (to fit)
+ worstP = -1; // point at which worst difference came up
+ for (int i = 1; i < N; i++) { // start at second point till the last one
+ Geom::Point nPt;
+ bool isForced = fk[i];
+ nPt[0] = Xk[i]; // get the point
+ nPt[1] = Yk[i];
+
+ double nle = DistanceToCubic(start, res, nPt); // distance from point. see the documentation on that function
+ if ( isForced ) {
+ // forced points are favored for splitting the recursion; we do this by increasing their distance
+ if ( worstP < 0 || 2 * nle > worstD ) { // exaggerating distance for the forced point?
+ worstP = i;
+ worstD = 2 * nle;
+ }
+ } else {
+ if ( worstP < 0 || nle > worstD ) { // if worstP not set or the distance for this point is greater than previous worse
+ worstP = i; // make this one the worse
+ worstD = nle;
+ }
+ }
+ }
+
+ g_free(tk);
+ g_free(Qk);
+ g_free(Xk);
+ g_free(Yk);
+ g_free(fk);
+ g_free(lk);
+
+ return false; // not sure why we return false? because the length of points to fit is zero?
+ // anyways, rare case, so fine to return false and a cubic bezier that starts
+ // at first and ends at last point with control points being same as
+ // endpoins.
+ }
+
+ // divide by total length to make "time" values. See documentation of fitting_table structure
+ double totLen = tk[N - 1];
+ for (int i = 1; i < N - 1; i++) {
+ tk[i] /= totLen;
+ }
+
+ res.p = end;
+ if ( FitCubic(start, res, Xk, Yk, Qk, tk, N) ) { // fit a cubic, if goes well, set cp1 and cp2
+ cp1 = start + res.start / 3; // this factor of three comes from the fact that this is how livarot stores them. See docs in path-description.h
+ cp2 = end + res.end / 3;
+ } else {
+ // aie, non-inversible
+ // okay we couldn't fit one, don't know when this would happen but probably due to matrix
+ // being non-inversible (no idea when that would happen either)
+
+ // same code from above, calculates error (kinda, see comments on DistanceToCubic) and returns false
+ res.start[0] = res.start[1] = 0;
+ res.end[0] = res.end[1] = 0;
+ double worstD = 0;
+ worstP = -1;
+ for (int i = 1; i < N; i++) {
+ Geom::Point nPt(Xk[i], Yk[i]);
+ bool isForced = fk[i];
+ double nle = DistanceToCubic(start, res, nPt);
+ if ( isForced ) {
+ // forced points are favored for splitting the recursion; we do this by increasing their distance
+ if ( worstP < 0 || 2 * nle > worstD ) {
+ worstP = i;
+ worstD = 2 * nle;
+ }
+ } else {
+ if ( worstP < 0 || nle > worstD ) {
+ worstP = i;
+ worstD = nle;
+ }
+ }
+ }
+
+ g_free(tk);
+ g_free(Qk);
+ g_free(Xk);
+ g_free(Yk);
+ g_free(fk);
+ g_free(lk);
+ return false;
+ }
+
+ // calcul du delta= pondere par les longueurs des segments
+ // if we are here, fitting went well, let's calculate error between the cubic bezier that we
+ // fit and the actual polyline
+ double delta = 0;
+ {
+ double worstD = 0;
+ worstP = -1;
+ Geom::Point prevAppP;
+ Geom::Point prevP;
+ double prevDist;
+ prevP[0] = Xk[0];
+ prevP[1] = Yk[0];
+ prevAppP = prevP; // le premier seulement
+ prevDist = 0;
+#ifdef with_splotch_killer // <-- ignore the splotch killer if you're new and go to the part where this (N <= 20) if's else is
+ // so the thing is, if the number of points you're fitting on are few, you can often have a
+ // fit where the error on your polyline points are zero but it's a terrible fit, for
+ // example imagine three point o------o-------o and an S fitting on these. Error is zero,
+ // but it's a terrible fit. So to fix this problem, we calculate error at mid points of the
+ // line segments too, to be a better judge, and if it's terrible, we just reject this fit
+ // and the parent algorithm will do something about it (fit a smaller one or just do a line
+ // segment)
+ if ( N <= 20 ) {
+ for (int i = 1; i < N - 1; i++) // for every point that's not an endpoint
+ {
+ Geom::Point curAppP;
+ Geom::Point curP;
+ double curDist;
+ Geom::Point midAppP;
+ Geom::Point midP;
+ double midDist;
+
+ curAppP[0] = N13 (tk[i]) * cp1[0] + N23 (tk[i]) * cp2[0] + N03 (tk[i]) * Xk[0] + N33 (tk[i]) * Xk[N - 1]; // point on the curve
+ curAppP[1] = N13 (tk[i]) * cp1[1] + N23 (tk[i]) * cp2[1] + N03 (tk[i]) * Yk[0] + N33 (tk[i]) * Yk[N - 1];
+ curP[0] = Xk[i]; // polyline point
+ curP[1] = Yk[i];
+ midAppP[0] = N13 (0.5*(tk[i]+tk[i-1])) * cp1[0] + N23 (0.5*(tk[i]+tk[i-1])) * cp2[0] + N03 (0.5*(tk[i]+tk[i-1])) * Xk[0] + N33 (0.5*(tk[i]+tk[i-1])) * Xk[N - 1]; // midpoint on the curve
+ midAppP[1] = N13 (0.5*(tk[i]+tk[i-1])) * cp1[1] + N23 (0.5*(tk[i]+tk[i-1])) * cp2[1] + N03 (0.5*(tk[i]+tk[i-1])) * Yk[0] + N33 (0.5*(tk[i]+tk[i-1])) * Yk[N - 1];
+ midP=0.5*(curP+prevP); // midpoint on the polyline
+
+ Geom::Point diff;
+ diff = curAppP-curP;
+ curDist = dot(diff,diff); // squared distance between point on cubic curve and the polyline point
+
+ diff = midAppP-midP;
+ midDist = dot(diff,diff); // squared distance between midpoint on polyline and the equivalent on cubic curve
+
+ delta+=0.3333*(curDist+prevDist+midDist)/**lk[i]*/; // The multiplication by lk[i] here kind of creates a weightage mechanism
+ // we divide delta by total length afterwards, so line segments with more share in
+ // the length get weighted more. Why do we care about prevDist though? Anyways,
+ // it's a way of measuring error, so probably fine
+
+ if ( curDist > worstD ) { // worst error management
+ worstD = curDist;
+ worstP = i;
+ } else if ( fk[i] && 2*curDist > worstD ) {
+ worstD = 2*curDist;
+ worstP = i;
+ }
+ prevP = curP;
+ prevAppP = curAppP; // <-- useless
+ prevDist = curDist;
+ }
+ delta/=totLen;
+ } else {
+#endif
+ for (int i = 1; i < N - 1; i++) // for each point in the polyline that's not an end point
+ {
+ Geom::Point curAppP; // the current point on the bezier patch
+ Geom::Point curP; // the polyline point
+ double curDist;
+
+ // https://en.wikipedia.org/wiki/B%C3%A9zier_curve
+ curAppP[0] = N13 (tk[i]) * cp1[0] + N23 (tk[i]) * cp2[0] + N03 (tk[i]) * Xk[0] + N33 (tk[i]) * Xk[N - 1]; // <-- cubic bezier function
+ curAppP[1] = N13 (tk[i]) * cp1[1] + N23 (tk[i]) * cp2[1] + N03 (tk[i]) * Yk[0] + N33 (tk[i]) * Yk[N - 1];
+ curP[0] = Xk[i];
+ curP[1] = Yk[i];
+
+ Geom::Point diff;
+ diff = curAppP-curP; // difference between the two
+ curDist = dot(diff,diff); // square of the difference
+ delta += curDist; // add to total error
+ if ( curDist > worstD ) { // management of worst error (max finder basically)
+ worstD = curDist;
+ worstP = i;
+ } else if ( fk[i] && 2*curDist > worstD ) { // it wasn't the worse, but if it's forced point, judge more harshly
+ worstD = 2*curDist;
+ worstP = i;
+ }
+ prevP = curP;
+ prevAppP = curAppP; // <-- we are not using these
+ prevDist = curDist; // <-- we are not using these
+ }
+#ifdef with_splotch_killer
+ }
+#endif
+ }
+
+ // are we fine with the error? Compare it to threshold squared
+ if (delta < treshhold * treshhold)
+ {
+ // premier jet
+ res.start = 3.0 * (cp1 - start); // calculate handles
+ res.end = -3.0 * (cp2 - end);
+ res.p = end;
+
+ // Refine a little.
+ for (int i = 1; i < N - 1; i++) // do Newton Raphson iterations
+ {
+ Geom::Point
+ pt;
+ pt[0] = Xk[i];
+ pt[1] = Yk[i];
+ tk[i] = RaffineTk (pt, start, cp1, cp2, end, tk[i]);
+ if (tk[i] < tk[i - 1])
+ {
+ // Force tk to be monotonic non-decreasing.
+ tk[i] = tk[i - 1];
+ }
+ }
+
+ if ( FitCubic(start,res,Xk,Yk,Qk,tk,N) ) { // fit again and this should work
+ } else { // just in case it doesn't
+ // ca devrait jamais arriver, mais bon
+ res.start = 3.0 * (cp1 - start);
+ res.end = -3.0 * (cp2 - end);
+ g_free(tk);
+ g_free(Qk);
+ g_free(Xk);
+ g_free(Yk);
+ g_free(fk);
+ g_free(lk);
+ return true;
+ }
+ double ndelta = 0; // the same error computing stuff with and without splotch killer as before
+ {
+ double worstD = 0;
+ worstP = -1;
+ Geom::Point prevAppP;
+ Geom::Point prevP;
+ double prevDist;
+ prevP[0] = Xk[0];
+ prevP[1] = Yk[0];
+ prevAppP = prevP; // le premier seulement
+ prevDist = 0;
+#ifdef with_splotch_killer
+ if ( N <= 20 ) {
+ for (int i = 1; i < N - 1; i++)
+ {
+ Geom::Point curAppP;
+ Geom::Point curP;
+ double curDist;
+ Geom::Point midAppP;
+ Geom::Point midP;
+ double midDist;
+
+ curAppP[0] = N13 (tk[i]) * cp1[0] + N23 (tk[i]) * cp2[0] + N03 (tk[i]) * Xk[0] + N33 (tk[i]) * Xk[N - 1];
+ curAppP[1] = N13 (tk[i]) * cp1[1] + N23 (tk[i]) * cp2[1] + N03 (tk[i]) * Yk[0] + N33 (tk[i]) * Yk[N - 1];
+ curP[0] = Xk[i];
+ curP[1] = Yk[i];
+ midAppP[0] = N13 (0.5*(tk[i]+tk[i-1])) * cp1[0] + N23 (0.5*(tk[i]+tk[i-1])) * cp2[0] + N03 (0.5*(tk[i]+tk[i-1])) * Xk[0] + N33 (0.5*(tk[i]+tk[i-1])) * Xk[N - 1];
+ midAppP[1] = N13 (0.5*(tk[i]+tk[i-1])) * cp1[1] + N23 (0.5*(tk[i]+tk[i-1])) * cp2[1] + N03 (0.5*(tk[i]+tk[i-1])) * Yk[0] + N33 (0.5*(tk[i]+tk[i-1])) * Yk[N - 1];
+ midP = 0.5*(curP+prevP);
+
+ Geom::Point diff;
+ diff = curAppP-curP;
+ curDist = dot(diff,diff);
+ diff = midAppP-midP;
+ midDist = dot(diff,diff);
+
+ ndelta+=0.3333*(curDist+prevDist+midDist)/**lk[i]*/;
+
+ if ( curDist > worstD ) {
+ worstD = curDist;
+ worstP = i;
+ } else if ( fk[i] && 2*curDist > worstD ) {
+ worstD = 2*curDist;
+ worstP = i;
+ }
+ prevP = curP;
+ prevAppP = curAppP;
+ prevDist = curDist;
+ }
+ ndelta /= totLen;
+ } else {
+#endif
+ for (int i = 1; i < N - 1; i++)
+ {
+ Geom::Point curAppP;
+ Geom::Point curP;
+ double curDist;
+
+ curAppP[0] = N13 (tk[i]) * cp1[0] + N23 (tk[i]) * cp2[0] + N03 (tk[i]) * Xk[0] + N33 (tk[i]) * Xk[N - 1];
+ curAppP[1] = N13 (tk[i]) * cp1[1] + N23 (tk[i]) * cp2[1] + N03 (tk[i]) * Yk[0] + N33 (tk[i]) * Yk[N - 1];
+ curP[0]=Xk[i];
+ curP[1]=Yk[i];
+
+ Geom::Point diff;
+ diff=curAppP-curP;
+ curDist=dot(diff,diff);
+ ndelta+=curDist;
+
+ if ( curDist > worstD ) {
+ worstD=curDist;
+ worstP=i;
+ } else if ( fk[i] && 2*curDist > worstD ) {
+ worstD=2*curDist;
+ worstP=i;
+ }
+ prevP=curP;
+ prevAppP=curAppP;
+ prevDist=curDist;
+ }
+#ifdef with_splotch_killer
+ }
+#endif
+ }
+
+ g_free(tk);
+ g_free(Qk);
+ g_free(Xk);
+ g_free(Yk);
+ g_free(fk);
+ g_free(lk);
+
+ if (ndelta < delta + 0.00001) // is the new one after newton raphson better? they are stored in "res"
+ {
+ return true; // okay great return those handles.
+ } else {
+ // nothing better to do
+ res.start = 3.0 * (cp1 - start); // new ones aren't better? use the old ones stored in cp1 and cp2
+ res.end = -3.0 * (cp2 - end);
+ }
+ return true;
+ } else { // threshold got disrespected, return false
+ // nothing better to do
+ }
+
+ g_free(tk);
+ g_free(Qk);
+ g_free(Xk);
+ g_free(Yk);
+ g_free(fk);
+ g_free(lk);
+ return false;
+}
+
+double Path::RaffineTk (Geom::Point pt, Geom::Point p0, Geom::Point p1, Geom::Point p2, Geom::Point p3, double it)
+{
+ // Refinement of the tk values.
+ // Just one iteration of Newtow Raphson, given that we're approaching the curve anyway.
+ // [fr: vu que de toute facon la courbe est approchC)e]
+ double const Ax = pt[Geom::X] -
+ p0[Geom::X] * N03(it) -
+ p1[Geom::X] * N13(it) -
+ p2[Geom::X] * N23(it) -
+ p3[Geom::X] * N33(it);
+
+ double const Bx = (p1[Geom::X] - p0[Geom::X]) * N02(it) +
+ (p2[Geom::X] - p1[Geom::X]) * N12(it) +
+ (p3[Geom::X] - p2[Geom::X]) * N22(it);
+
+ double const Cx = (p0[Geom::X] - 2 * p1[Geom::X] + p2[Geom::X]) * N01(it) +
+ (p3[Geom::X] - 2 * p2[Geom::X] + p1[Geom::X]) * N11(it);
+
+ double const Ay = pt[Geom::Y] -
+ p0[Geom::Y] * N03(it) -
+ p1[Geom::Y] * N13(it) -
+ p2[Geom::Y] * N23(it) -
+ p3[Geom::Y] * N33(it);
+
+ double const By = (p1[Geom::Y] - p0[Geom::Y]) * N02(it) +
+ (p2[Geom::Y] - p1[Geom::Y]) * N12(it) +
+ (p3[Geom::Y] - p2[Geom::Y]) * N22(it);
+
+ double const Cy = (p0[Geom::Y] - 2 * p1[Geom::Y] + p2[Geom::Y]) * N01(it) +
+ (p3[Geom::Y] - 2 * p2[Geom::Y] + p1[Geom::Y]) * N11(it);
+
+ double const dF = -6 * (Ax * Bx + Ay * By);
+ double const ddF = 18 * (Bx * Bx + By * By) - 12 * (Ax * Cx + Ay * Cy);
+ if (fabs (ddF) > 0.0000001) {
+ return it - dF / ddF;
+ }
+
+ return it;
+}
+
+// Variation on the fitting theme: try to merge path commands into cubic bezier patches.
+// The goal is to reduce the number of path commands, especially when operations on path produce
+// lots of small path elements; ideally you could get rid of very small segments at reduced visual cost.
+void Path::Coalesce(double tresh)
+{
+ if ( descr_flags & descr_adding_bezier ) {
+ CancelBezier();
+ }
+
+ if ( descr_flags & descr_doing_subpath ) {
+ CloseSubpath();
+ }
+
+ if (descr_cmd.size() <= 2) {
+ return;
+ }
+
+ SetBackData(false);
+ Path* tempDest = new Path();
+ tempDest->SetBackData(false);
+
+ ConvertEvenLines(0.25*tresh);
+
+ int lastP = 0;
+ int lastAP = -1;
+ // As the elements are stored in a separate array, it's no longer worth optimizing
+ // the rewriting in the same array.
+ // [[comme les elements sont stockes dans un tableau a part, plus la peine d'optimiser
+ // la réécriture dans la meme tableau]]
+
+ int lastA = descr_cmd[0]->associated;
+ int prevA = lastA;
+ Geom::Point firstP;
+
+ /* FIXME: the use of this variable probably causes a leak or two.
+ ** It's a hack anyway, and probably only needs to be a type rather than
+ ** a full PathDescr.
+ */
+ std::unique_ptr<PathDescr> lastAddition(new PathDescrMoveTo(Geom::Point(0, 0)));
+ bool containsForced = false;
+ PathDescrCubicTo pending_cubic(Geom::Point(0, 0), Geom::Point(0, 0), Geom::Point(0, 0));
+
+ for (int curP = 0; curP < int(descr_cmd.size()); curP++) {
+ int typ = descr_cmd[curP]->getType();
+ int nextA = lastA;
+
+ if (typ == descr_moveto) {
+
+ if (lastAddition->flags != descr_moveto) {
+ FlushPendingAddition(tempDest,lastAddition.get(),pending_cubic,lastAP);
+ }
+ lastAddition.reset(descr_cmd[curP]->clone());
+ lastAP = curP;
+ FlushPendingAddition(tempDest, lastAddition.get(), pending_cubic, lastAP);
+ // Added automatically (too bad about multiple moveto's).
+ // [fr: (tant pis pour les moveto multiples)]
+ containsForced = false;
+
+ PathDescrMoveTo *nData = dynamic_cast<PathDescrMoveTo *>(descr_cmd[curP]);
+ firstP = nData->p;
+ lastA = descr_cmd[curP]->associated;
+ prevA = lastA;
+ lastP = curP;
+
+ } else if (typ == descr_close) {
+ nextA = descr_cmd[curP]->associated;
+ if (lastAddition->flags != descr_moveto) {
+
+ PathDescrCubicTo res(Geom::Point(0, 0), Geom::Point(0, 0), Geom::Point(0, 0));
+ int worstP = -1;
+ if (AttemptSimplify(lastA, nextA - lastA + 1, (containsForced) ? 0.05 * tresh : tresh, res, worstP)) {
+ lastAddition.reset(new PathDescrCubicTo(Geom::Point(0, 0),
+ Geom::Point(0, 0),
+ Geom::Point(0, 0)));
+ pending_cubic = res;
+ lastAP = -1;
+ }
+
+ FlushPendingAddition(tempDest, lastAddition.get(), pending_cubic, lastAP);
+ FlushPendingAddition(tempDest, descr_cmd[curP], pending_cubic, curP);
+
+ } else {
+ FlushPendingAddition(tempDest,descr_cmd[curP],pending_cubic,curP);
+ }
+
+ containsForced = false;
+ lastAddition.reset(new PathDescrMoveTo(Geom::Point(0, 0)));
+ prevA = lastA = nextA;
+ lastP = curP;
+ lastAP = curP;
+
+ } else if (typ == descr_forced) {
+
+ nextA = descr_cmd[curP]->associated;
+ if (lastAddition->flags != descr_moveto) {
+
+ PathDescrCubicTo res(Geom::Point(0, 0), Geom::Point(0, 0), Geom::Point(0, 0));
+ int worstP = -1;
+ if (AttemptSimplify(lastA, nextA - lastA + 1, 0.05 * tresh, res, worstP)) {
+ // plus sensible parce que point force
+ // ca passe
+ /* (Possible translation: More sensitive because contains a forced point.) */
+ containsForced = true;
+ } else {
+ // Force the addition.
+ FlushPendingAddition(tempDest, lastAddition.get(), pending_cubic, lastAP);
+ lastAddition.reset(new PathDescrMoveTo(Geom::Point(0, 0)));
+ prevA = lastA = nextA;
+ lastP = curP;
+ lastAP = curP;
+ containsForced = false;
+ }
+ }
+
+ } else if (typ == descr_lineto || typ == descr_cubicto || typ == descr_arcto) {
+
+ nextA = descr_cmd[curP]->associated;
+ if (lastAddition->flags != descr_moveto) {
+
+ PathDescrCubicTo res(Geom::Point(0, 0), Geom::Point(0, 0), Geom::Point(0, 0));
+ int worstP = -1;
+ if (AttemptSimplify(lastA, nextA - lastA + 1, tresh, res, worstP)) {
+ lastAddition.reset(new PathDescrCubicTo(Geom::Point(0, 0),
+ Geom::Point(0, 0),
+ Geom::Point(0, 0)));
+ pending_cubic = res;
+ lastAddition->associated = lastA;
+ lastP = curP;
+ lastAP = -1;
+ } else {
+ lastA = descr_cmd[lastP]->associated; // pourrait etre surecrit par la ligne suivante
+ /* (possible translation: Could be overwritten by the next line.) */
+ FlushPendingAddition(tempDest, lastAddition.get(), pending_cubic, lastAP);
+ lastAddition.reset(descr_cmd[curP]->clone());
+ if ( typ == descr_cubicto ) {
+ pending_cubic = *(dynamic_cast<PathDescrCubicTo*>(descr_cmd[curP]));
+ }
+ lastAP = curP;
+ containsForced = false;
+ }
+
+ } else {
+ lastA = prevA /*descr_cmd[curP-1]->associated */ ;
+ lastAddition.reset(descr_cmd[curP]->clone());
+ if ( typ == descr_cubicto ) {
+ pending_cubic = *(dynamic_cast<PathDescrCubicTo*>(descr_cmd[curP]));
+ }
+ lastAP = curP;
+ containsForced = false;
+ }
+ prevA = nextA;
+
+ } else if (typ == descr_bezierto) {
+
+ if (lastAddition->flags != descr_moveto) {
+ FlushPendingAddition(tempDest, lastAddition.get(), pending_cubic, lastAP);
+ lastAddition.reset(new PathDescrMoveTo(Geom::Point(0, 0)));
+ }
+ lastAP = -1;
+ lastA = descr_cmd[curP]->associated;
+ lastP = curP;
+ PathDescrBezierTo *nBData = dynamic_cast<PathDescrBezierTo*>(descr_cmd[curP]);
+ for (int i = 1; i <= nBData->nb; i++) {
+ FlushPendingAddition(tempDest, descr_cmd[curP + i], pending_cubic, curP + i);
+ }
+ curP += nBData->nb;
+ prevA = nextA;
+
+ } else if (typ == descr_interm_bezier) {
+ continue;
+ } else {
+ continue;
+ }
+ }
+
+ if (lastAddition->flags != descr_moveto) {
+ FlushPendingAddition(tempDest, lastAddition.get(), pending_cubic, lastAP);
+ }
+
+ Copy(tempDest);
+ delete tempDest;
+}
+
+
+void Path::FlushPendingAddition(Path *dest, PathDescr *lastAddition,
+ PathDescrCubicTo &lastCubic, int lastAP)
+{
+ switch (lastAddition->getType()) {
+
+ case descr_moveto:
+ if ( lastAP >= 0 ) {
+ PathDescrMoveTo* nData = dynamic_cast<PathDescrMoveTo *>(descr_cmd[lastAP]);
+ dest->MoveTo(nData->p);
+ }
+ break;
+
+ case descr_close:
+ dest->Close();
+ break;
+
+ case descr_cubicto:
+ dest->CubicTo(lastCubic.p, lastCubic.start, lastCubic.end);
+ break;
+
+ case descr_lineto:
+ if ( lastAP >= 0 ) {
+ PathDescrLineTo *nData = dynamic_cast<PathDescrLineTo *>(descr_cmd[lastAP]);
+ dest->LineTo(nData->p);
+ }
+ break;
+
+ case descr_arcto:
+ if ( lastAP >= 0 ) {
+ PathDescrArcTo *nData = dynamic_cast<PathDescrArcTo *>(descr_cmd[lastAP]);
+ dest->ArcTo(nData->p, nData->rx, nData->ry, nData->angle, nData->large, nData->clockwise);
+ }
+ break;
+
+ case descr_bezierto:
+ if ( lastAP >= 0 ) {
+ PathDescrBezierTo *nData = dynamic_cast<PathDescrBezierTo *>(descr_cmd[lastAP]);
+ dest->BezierTo(nData->p);
+ }
+ break;
+
+ case descr_interm_bezier:
+ if ( lastAP >= 0 ) {
+ PathDescrIntermBezierTo *nData = dynamic_cast<PathDescrIntermBezierTo*>(descr_cmd[lastAP]);
+ dest->IntermBezierTo(nData->p);
+ }
+ break;
+ }
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/livarot/PathStroke.cpp b/src/livarot/PathStroke.cpp
new file mode 100644
index 0000000..e70ce36
--- /dev/null
+++ b/src/livarot/PathStroke.cpp
@@ -0,0 +1,763 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * TODO: insert short description here
+ *//*
+ * Authors:
+ * see git history
+ * Fred
+ *
+ * Copyright (C) 2018 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "Path.h"
+#include "Shape.h"
+#include <2geom/transforms.h>
+
+/*
+ * stroking polylines into a Shape instance
+ * grunt work.
+ * if the goal is to raster the stroke, polyline stroke->polygon->uncrossed polygon->raster is grossly
+ * inefficient (but reuse the intersector, so that's what a lazy programmer like me does). the correct way would be
+ * to set up a supersampled buffer, raster each polyline stroke's part (one part per segment in the polyline, plus
+ * each join) because these are all convex polygons, then transform in alpha values
+ */
+
+// until i find something better
+static Geom::Point StrokeNormalize(const Geom::Point value) {
+ double length = L2(value);
+ if ( length < 0.0000001 ) {
+ return Geom::Point(0, 0);
+ } else {
+ return value/length;
+ }
+}
+
+// faster version if length is known
+static Geom::Point StrokeNormalize(const Geom::Point value, double length) {
+ if ( length < 0.0000001 ) {
+ return Geom::Point(0, 0);
+ } else {
+ return value/length;
+ }
+}
+
+void Path::Stroke(Shape *dest, bool doClose, double width, JoinType join,
+ ButtType butt, double miter, bool justAdd)
+{
+ if (dest == nullptr) {
+ return;
+ }
+
+ if (justAdd == false) {
+ dest->Reset(3 * pts.size(), 3 * pts.size());
+ }
+
+ dest->MakeBackData(false);
+
+ int lastM = 0;
+ while (lastM < int(pts.size())) {
+
+ int lastP = lastM + 1;
+ while (lastP < int(pts.size()) // select one subpath
+ && (pts[lastP].isMoveTo == polyline_lineto
+ || pts[lastP].isMoveTo == polyline_forced))
+ {
+ lastP++;
+ }
+
+ if ( lastP > lastM+1 ) {
+ Geom::Point sbStart = pts[lastM].p;
+ Geom::Point sbEnd = pts[lastP - 1].p;
+ // if ( pts[lastP - 1].closed ) { // this is correct, but this bugs text rendering (doesn't close text stroke)...
+ if ( Geom::LInfty(sbEnd-sbStart) < 0.00001 ) { // why close lines that shouldn't be closed?
+ // ah I see, because close is defined here for
+ // a whole path and should be defined per subpath.
+ // debut==fin => ferme (on devrait garder un element pour les close(), mais tant pis)
+ DoStroke(lastM, lastP - lastM, dest, true, width, join, butt, miter, true);
+ } else {
+ DoStroke(lastM, lastP - lastM, dest, doClose, width, join, butt, miter, true);
+ }
+ } else if (butt == butt_round) { // special case: zero length round butt is a circle
+ int last[2] = { -1, -1 };
+ Geom::Point dir;
+ dir[0] = 1;
+ dir[1] = 0;
+ Geom::Point pos = pts[lastM].p;
+ DoButt(dest, width, butt, pos, dir, last[RIGHT], last[LEFT]);
+ int end[2];
+ dir = -dir;
+ DoButt(dest, width, butt, pos, dir, end[LEFT], end[RIGHT]);
+ dest->AddEdge (end[LEFT], last[LEFT]);
+ dest->AddEdge (last[RIGHT], end[RIGHT]);
+ }
+ lastM = lastP;
+ }
+}
+
+void Path::DoStroke(int off, int N, Shape *dest, bool doClose, double width, JoinType join,
+ ButtType butt, double miter, bool /*justAdd*/)
+{
+ if (N <= 1) {
+ return;
+ }
+
+ Geom::Point prevP, nextP;
+ int prevI, nextI;
+ int upTo;
+
+ int curI = 0;
+ Geom::Point curP = pts[off].p;
+
+ if (doClose) {
+
+ prevI = N - 1;
+ while (prevI > 0) {
+ prevP = pts[off + prevI].p;
+ Geom::Point diff = curP - prevP;
+ double dist = dot(diff, diff);
+ if (dist > 0.001) {
+ break;
+ }
+ prevI--;
+ }
+ if (prevI <= 0) {
+ return;
+ }
+ upTo = prevI;
+
+ } else {
+
+ prevP = curP;
+ prevI = curI;
+ upTo = N - 1;
+ }
+
+ {
+ nextI = 1;
+ while (nextI <= upTo) {
+ nextP = pts[off + nextI].p;
+ Geom::Point diff = curP - nextP;
+ double dist = dot(diff, diff);
+ if (dist > 0.0) { // more tolerance for the first distance, to give the cap the right direction
+ break;
+ }
+ nextI++;
+ }
+ if (nextI > upTo) {
+ if (butt == butt_round) { // special case: zero length round butt is a circle
+ int last[2] = { -1, -1 };
+ Geom::Point dir;
+ dir[0] = 1;
+ dir[1] = 0;
+ DoButt(dest, width, butt, curP, dir, last[RIGHT], last[LEFT]);
+ int end[2];
+ dir = -dir;
+ DoButt(dest, width, butt, curP, dir, end[LEFT], end[RIGHT]);
+ dest->AddEdge (end[LEFT], last[LEFT]);
+ dest->AddEdge (last[RIGHT], end[RIGHT]);
+ }
+ return;
+ }
+ }
+
+ int start[2] = { -1, -1 };
+ int last[2] = { -1, -1 };
+ Geom::Point prevD = curP - prevP;
+ Geom::Point nextD = nextP - curP;
+ double prevLe = Geom::L2(prevD);
+ double nextLe = Geom::L2(nextD);
+ prevD = StrokeNormalize(prevD, prevLe);
+ nextD = StrokeNormalize(nextD, nextLe);
+
+ if (doClose) {
+ DoJoin(dest, width, join, curP, prevD, nextD, miter, prevLe, nextLe, start, last);
+ } else {
+ nextD = -nextD;
+ DoButt(dest, width, butt, curP, nextD, last[RIGHT], last[LEFT]);
+ nextD = -nextD;
+ }
+
+ do {
+ prevP = curP;
+ prevI = curI;
+ curP = nextP;
+ curI = nextI;
+ prevD = nextD;
+ prevLe = nextLe;
+ nextI++;
+ while (nextI <= upTo) {
+ nextP = pts[off + nextI].p;
+ Geom::Point diff = curP - nextP;
+ double dist = dot(diff, diff);
+ if (dist > 0.001 || (nextI == upTo && dist > 0.0)) { // more tolerance for the last distance too, for the right cap direction
+ break;
+ }
+ nextI++;
+ }
+ if (nextI > upTo) {
+ break;
+ }
+
+ nextD = nextP - curP;
+ nextLe = Geom::L2(nextD);
+ nextD = StrokeNormalize(nextD, nextLe);
+ int nSt[2] = { -1, -1 };
+ int nEn[2] = { -1, -1 };
+ DoJoin(dest, width, join, curP, prevD, nextD, miter, prevLe, nextLe, nSt, nEn);
+ dest->AddEdge(nSt[LEFT], last[LEFT]);
+ last[LEFT] = nEn[LEFT];
+ dest->AddEdge(last[RIGHT], nSt[RIGHT]);
+ last[RIGHT] = nEn[RIGHT];
+ } while (nextI <= upTo);
+
+ if (doClose) {
+ /* prevP=curP;
+ prevI=curI;
+ curP=nextP;
+ curI=nextI;
+ prevD=nextD;*/
+ nextP = pts[off].p;
+
+ nextD = nextP - curP;
+ nextLe = Geom::L2(nextD);
+ nextD = StrokeNormalize(nextD, nextLe);
+ int nSt[2] = { -1, -1 };
+ int nEn[2] = { -1, -1 };
+ DoJoin(dest, width, join, curP, prevD, nextD, miter, prevLe, nextLe, nSt, nEn);
+ dest->AddEdge (nSt[LEFT], last[LEFT]);
+ last[LEFT] = nEn[LEFT];
+ dest->AddEdge (last[RIGHT], nSt[RIGHT]);
+ last[RIGHT] = nEn[RIGHT];
+
+ dest->AddEdge (start[LEFT], last[LEFT]);
+ dest->AddEdge (last[RIGHT], start[RIGHT]);
+
+ } else {
+
+ int end[2];
+ DoButt (dest, width, butt, curP, prevD, end[LEFT], end[RIGHT]);
+ dest->AddEdge (end[LEFT], last[LEFT]);
+ dest->AddEdge (last[RIGHT], end[RIGHT]);
+ }
+}
+
+
+void Path::DoButt(Shape *dest, double width, ButtType butt, Geom::Point pos, Geom::Point dir,
+ int &leftNo, int &rightNo)
+{
+ Geom::Point nor;
+ nor = dir.ccw();
+
+ if (butt == butt_square)
+ {
+ Geom::Point x;
+ x = pos + width * dir + width * nor;
+ int bleftNo = dest->AddPoint (x);
+ x = pos + width * dir - width * nor;
+ int brightNo = dest->AddPoint (x);
+ x = pos + width * nor;
+ leftNo = dest->AddPoint (x);
+ x = pos - width * nor;
+ rightNo = dest->AddPoint (x);
+ dest->AddEdge (rightNo, brightNo);
+ dest->AddEdge (brightNo, bleftNo);
+ dest->AddEdge (bleftNo, leftNo);
+ }
+ else if (butt == butt_pointy)
+ {
+ leftNo = dest->AddPoint (pos + width * nor);
+ rightNo = dest->AddPoint (pos - width * nor);
+ int mid = dest->AddPoint (pos + width * dir);
+ dest->AddEdge (rightNo, mid);
+ dest->AddEdge (mid, leftNo);
+ }
+ else if (butt == butt_round)
+ {
+ const Geom::Point sx = pos + width * nor;
+ const Geom::Point ex = pos - width * nor;
+ leftNo = dest->AddPoint (sx);
+ rightNo = dest->AddPoint (ex);
+
+ RecRound (dest, rightNo, leftNo, ex, sx, -nor, nor, pos, width);
+ }
+ else
+ {
+ leftNo = dest->AddPoint (pos + width * nor);
+ rightNo = dest->AddPoint (pos - width * nor);
+ dest->AddEdge (rightNo, leftNo);
+ }
+}
+
+
+void Path::DoJoin (Shape *dest, double width, JoinType join, Geom::Point pos, Geom::Point prev,
+ Geom::Point next, double miter, double /*prevL*/, double /*nextL*/,
+ int *stNo, int *enNo)
+{
+ Geom::Point pnor = prev.ccw();
+ Geom::Point nnor = next.ccw();
+ double angSi = cross(prev, next);
+
+ /* FIXED: this special case caused bug 1028953 */
+ if (angSi > -0.0001 && angSi < 0.0001) {
+ double angCo = dot (prev, next);
+ if (angCo > 0.9999) {
+ // tout droit
+ stNo[LEFT] = enNo[LEFT] = dest->AddPoint(pos + width * pnor);
+ stNo[RIGHT] = enNo[RIGHT] = dest->AddPoint(pos - width * pnor);
+ } else {
+ // demi-tour
+ const Geom::Point sx = pos + width * pnor;
+ const Geom::Point ex = pos - width * pnor;
+ stNo[LEFT] = enNo[RIGHT] = dest->AddPoint (sx);
+ stNo[RIGHT] = enNo[LEFT] = dest->AddPoint (ex);
+ if (join == join_round) {
+ RecRound (dest, enNo[LEFT], stNo[LEFT], ex, sx, -pnor, pnor, pos, width);
+ dest->AddEdge(stNo[RIGHT], enNo[RIGHT]);
+ } else {
+ dest->AddEdge(enNo[LEFT], stNo[LEFT]);
+ dest->AddEdge(stNo[RIGHT], enNo[RIGHT]); // two times because both are crossing each other
+ }
+ }
+ return;
+ }
+
+ if (angSi < 0) {
+ int midNo = dest->AddPoint(pos);
+ stNo[LEFT] = dest->AddPoint(pos + width * pnor);
+ enNo[LEFT] = dest->AddPoint(pos + width * nnor);
+ dest->AddEdge(enNo[LEFT], midNo);
+ dest->AddEdge(midNo, stNo[LEFT]);
+
+ if (join == join_pointy) {
+
+ stNo[RIGHT] = dest->AddPoint(pos - width * pnor);
+ enNo[RIGHT] = dest->AddPoint(pos - width * nnor);
+
+ const Geom::Point biss = StrokeNormalize(prev - next);
+ double c2 = dot(biss, nnor);
+ double l = width / c2;
+ double emiter = width * c2;
+ if (emiter < miter) {
+ emiter = miter;
+ }
+
+ if (fabs(l) < miter) {
+ int const n = dest->AddPoint(pos - l * biss);
+ dest->AddEdge(stNo[RIGHT], n);
+ dest->AddEdge(n, enNo[RIGHT]);
+ } else {
+ dest->AddEdge(stNo[RIGHT], enNo[RIGHT]);
+ }
+
+ } else if (join == join_round) {
+ Geom::Point sx = pos - width * pnor;
+ stNo[RIGHT] = dest->AddPoint(sx);
+ Geom::Point ex = pos - width * nnor;
+ enNo[RIGHT] = dest->AddPoint(ex);
+
+ RecRound(dest, stNo[RIGHT], enNo[RIGHT],
+ sx, ex, -pnor, -nnor, pos, width);
+
+ } else {
+ stNo[RIGHT] = dest->AddPoint(pos - width * pnor);
+ enNo[RIGHT] = dest->AddPoint(pos - width * nnor);
+ dest->AddEdge(stNo[RIGHT], enNo[RIGHT]);
+ }
+
+ } else {
+
+ int midNo = dest->AddPoint(pos);
+ stNo[RIGHT] = dest->AddPoint(pos - width * pnor);
+ enNo[RIGHT] = dest->AddPoint(pos - width * nnor);
+ dest->AddEdge(stNo[RIGHT], midNo);
+ dest->AddEdge(midNo, enNo[RIGHT]);
+
+ if (join == join_pointy) {
+
+ stNo[LEFT] = dest->AddPoint(pos + width * pnor);
+ enNo[LEFT] = dest->AddPoint(pos + width * nnor);
+
+ const Geom::Point biss = StrokeNormalize(next - prev);
+ double c2 = dot(biss, nnor);
+ double l = width / c2;
+ double emiter = width * c2;
+ if (emiter < miter) {
+ emiter = miter;
+ }
+ if ( fabs(l) < miter) {
+ int const n = dest->AddPoint (pos + l * biss);
+ dest->AddEdge (enNo[LEFT], n);
+ dest->AddEdge (n, stNo[LEFT]);
+ }
+ else
+ {
+ dest->AddEdge (enNo[LEFT], stNo[LEFT]);
+ }
+
+ } else if (join == join_round) {
+
+ Geom::Point sx = pos + width * pnor;
+ stNo[LEFT] = dest->AddPoint(sx);
+ Geom::Point ex = pos + width * nnor;
+ enNo[LEFT] = dest->AddPoint(ex);
+
+ RecRound(dest, enNo[LEFT], stNo[LEFT],
+ ex, sx, nnor, pnor, pos, width);
+
+ } else {
+ stNo[LEFT] = dest->AddPoint(pos + width * pnor);
+ enNo[LEFT] = dest->AddPoint(pos + width * nnor);
+ dest->AddEdge(enNo[LEFT], stNo[LEFT]);
+ }
+ }
+}
+
+ void
+Path::DoLeftJoin (Shape * dest, double width, JoinType join, Geom::Point pos,
+ Geom::Point prev, Geom::Point next, double miter, double /*prevL*/, double /*nextL*/,
+ int &leftStNo, int &leftEnNo,int pathID,int pieceID,double tID)
+{
+ Geom::Point pnor=prev.ccw();
+ Geom::Point nnor=next.ccw();
+ double angSi = cross(prev, next);
+ if (angSi > -0.0001 && angSi < 0.0001)
+ {
+ double angCo = dot (prev, next);
+ if (angCo > 0.9999)
+ {
+ // tout droit
+ leftEnNo = leftStNo = dest->AddPoint (pos + width * pnor);
+ }
+ else
+ {
+ // demi-tour
+ leftStNo = dest->AddPoint (pos + width * pnor);
+ leftEnNo = dest->AddPoint (pos - width * pnor);
+ int nEdge=dest->AddEdge (leftEnNo, leftStNo);
+ if ( dest->hasBackData() ) {
+ dest->ebData[nEdge].pathID=pathID;
+ dest->ebData[nEdge].pieceID=pieceID;
+ dest->ebData[nEdge].tSt=dest->ebData[nEdge].tEn=tID;
+ }
+ }
+ return;
+ }
+ if (angSi < 0)
+ {
+ /* Geom::Point biss;
+ biss.x=next.x-prev.x;
+ biss.y=next.y-prev.y;
+ double c2=cross(next, biss);
+ double l=width/c2;
+ double projn=l*(dot(biss,next));
+ double projp=-l*(dot(biss,prev));
+ if ( projp <= 0.5*prevL && projn <= 0.5*nextL ) {
+ double x,y;
+ x=pos.x+l*biss.x;
+ y=pos.y+l*biss.y;
+ leftEnNo=leftStNo=dest->AddPoint(x,y);
+ } else {*/
+ leftStNo = dest->AddPoint (pos + width * pnor);
+ leftEnNo = dest->AddPoint (pos + width * nnor);
+// int midNo = dest->AddPoint (pos);
+// int nEdge=dest->AddEdge (leftEnNo, midNo);
+ int nEdge=dest->AddEdge (leftEnNo, leftStNo);
+ if ( dest->hasBackData() ) {
+ dest->ebData[nEdge].pathID=pathID;
+ dest->ebData[nEdge].pieceID=pieceID;
+ dest->ebData[nEdge].tSt=dest->ebData[nEdge].tEn=tID;
+ }
+// nEdge=dest->AddEdge (midNo, leftStNo);
+// if ( dest->hasBackData() ) {
+// dest->ebData[nEdge].pathID=pathID;
+// dest->ebData[nEdge].pieceID=pieceID;
+// dest->ebData[nEdge].tSt=dest->ebData[nEdge].tEn=tID;
+// }
+ // }
+ }
+ else
+ {
+ if (join == join_pointy)
+ {
+ leftStNo = dest->AddPoint (pos + width * pnor);
+ leftEnNo = dest->AddPoint (pos + width * nnor);
+
+ const Geom::Point biss = StrokeNormalize (pnor + nnor);
+ double c2 = dot (biss, nnor);
+ double l = width / c2;
+ double emiter = width * c2;
+ if (emiter < miter)
+ emiter = miter;
+ if (l <= emiter)
+ {
+ int nleftStNo = dest->AddPoint (pos + l * biss);
+ int nEdge=dest->AddEdge (leftEnNo, nleftStNo);
+ if ( dest->hasBackData() ) {
+ dest->ebData[nEdge].pathID=pathID;
+ dest->ebData[nEdge].pieceID=pieceID;
+ dest->ebData[nEdge].tSt=dest->ebData[nEdge].tEn=tID;
+ }
+ nEdge=dest->AddEdge (nleftStNo, leftStNo);
+ if ( dest->hasBackData() ) {
+ dest->ebData[nEdge].pathID=pathID;
+ dest->ebData[nEdge].pieceID=pieceID;
+ dest->ebData[nEdge].tSt=dest->ebData[nEdge].tEn=tID;
+ }
+ }
+ else
+ {
+ double s2 = cross(nnor, biss);
+ double dec = (l - emiter) * c2 / s2;
+ const Geom::Point tbiss=biss.ccw();
+
+ int nleftStNo = dest->AddPoint (pos + emiter * biss + dec * tbiss);
+ int nleftEnNo = dest->AddPoint (pos + emiter * biss - dec * tbiss);
+ int nEdge=dest->AddEdge (nleftEnNo, nleftStNo);
+ if ( dest->hasBackData() ) {
+ dest->ebData[nEdge].pathID=pathID;
+ dest->ebData[nEdge].pieceID=pieceID;
+ dest->ebData[nEdge].tSt=dest->ebData[nEdge].tEn=tID;
+ }
+ nEdge=dest->AddEdge (leftEnNo, nleftEnNo);
+ if ( dest->hasBackData() ) {
+ dest->ebData[nEdge].pathID=pathID;
+ dest->ebData[nEdge].pieceID=pieceID;
+ dest->ebData[nEdge].tSt=dest->ebData[nEdge].tEn=tID;
+ }
+ nEdge=dest->AddEdge (nleftStNo, leftStNo);
+ if ( dest->hasBackData() ) {
+ dest->ebData[nEdge].pathID=pathID;
+ dest->ebData[nEdge].pieceID=pieceID;
+ dest->ebData[nEdge].tSt=dest->ebData[nEdge].tEn=tID;
+ }
+ }
+ }
+ else if (join == join_round)
+ {
+ const Geom::Point sx = pos + width * pnor;
+ leftStNo = dest->AddPoint (sx);
+ const Geom::Point ex = pos + width * nnor;
+ leftEnNo = dest->AddPoint (ex);
+
+ RecRound(dest, leftEnNo, leftStNo,
+ sx, ex, pnor, nnor ,pos, width);
+
+ }
+ else
+ {
+ leftStNo = dest->AddPoint (pos + width * pnor);
+ leftEnNo = dest->AddPoint (pos + width * nnor);
+ int nEdge=dest->AddEdge (leftEnNo, leftStNo);
+ if ( dest->hasBackData() ) {
+ dest->ebData[nEdge].pathID=pathID;
+ dest->ebData[nEdge].pieceID=pieceID;
+ dest->ebData[nEdge].tSt=dest->ebData[nEdge].tEn=tID;
+ }
+ }
+ }
+}
+ void
+Path::DoRightJoin (Shape * dest, double width, JoinType join, Geom::Point pos,
+ Geom::Point prev, Geom::Point next, double miter, double /*prevL*/,
+ double /*nextL*/, int &rightStNo, int &rightEnNo,int pathID,int pieceID,double tID)
+{
+ const Geom::Point pnor=prev.ccw();
+ const Geom::Point nnor=next.ccw();
+ double angSi = cross(prev, next);
+ if (angSi > -0.0001 && angSi < 0.0001)
+ {
+ double angCo = dot (prev, next);
+ if (angCo > 0.9999)
+ {
+ // tout droit
+ rightEnNo = rightStNo = dest->AddPoint (pos - width*pnor);
+ }
+ else
+ {
+ // demi-tour
+ rightEnNo = dest->AddPoint (pos + width*pnor);
+ rightStNo = dest->AddPoint (pos - width*pnor);
+ int nEdge=dest->AddEdge (rightStNo, rightEnNo);
+ if ( dest->hasBackData() ) {
+ dest->ebData[nEdge].pathID=pathID;
+ dest->ebData[nEdge].pieceID=pieceID;
+ dest->ebData[nEdge].tSt=dest->ebData[nEdge].tEn=tID;
+ }
+ }
+ return;
+ }
+ if (angSi < 0)
+ {
+ if (join == join_pointy)
+ {
+ rightStNo = dest->AddPoint (pos - width*pnor);
+ rightEnNo = dest->AddPoint (pos - width*nnor);
+
+ const Geom::Point biss = StrokeNormalize (pnor + nnor);
+ double c2 = dot (biss, nnor);
+ double l = width / c2;
+ double emiter = width * c2;
+ if (emiter < miter)
+ emiter = miter;
+ if (l <= emiter)
+ {
+ int nrightStNo = dest->AddPoint (pos - l * biss);
+ int nEdge=dest->AddEdge (rightStNo, nrightStNo);
+ if ( dest->hasBackData() ) {
+ dest->ebData[nEdge].pathID=pathID;
+ dest->ebData[nEdge].pieceID=pieceID;
+ dest->ebData[nEdge].tSt=dest->ebData[nEdge].tEn=tID;
+ }
+ nEdge=dest->AddEdge (nrightStNo, rightEnNo);
+ if ( dest->hasBackData() ) {
+ dest->ebData[nEdge].pathID=pathID;
+ dest->ebData[nEdge].pieceID=pieceID;
+ dest->ebData[nEdge].tSt=dest->ebData[nEdge].tEn=tID;
+ }
+ }
+ else
+ {
+ double s2 = cross(nnor, biss);
+ double dec = (l - emiter) * c2 / s2;
+ const Geom::Point tbiss=biss.ccw();
+
+ int nrightStNo = dest->AddPoint (pos - emiter*biss - dec*tbiss);
+ int nrightEnNo = dest->AddPoint (pos - emiter*biss + dec*tbiss);
+ int nEdge=dest->AddEdge (rightStNo, nrightStNo);
+ if ( dest->hasBackData() ) {
+ dest->ebData[nEdge].pathID=pathID;
+ dest->ebData[nEdge].pieceID=pieceID;
+ dest->ebData[nEdge].tSt=dest->ebData[nEdge].tEn=tID;
+ }
+ nEdge=dest->AddEdge (nrightStNo, nrightEnNo);
+ if ( dest->hasBackData() ) {
+ dest->ebData[nEdge].pathID=pathID;
+ dest->ebData[nEdge].pieceID=pieceID;
+ dest->ebData[nEdge].tSt=dest->ebData[nEdge].tEn=tID;
+ }
+ nEdge=dest->AddEdge (nrightEnNo, rightEnNo);
+ if ( dest->hasBackData() ) {
+ dest->ebData[nEdge].pathID=pathID;
+ dest->ebData[nEdge].pieceID=pieceID;
+ dest->ebData[nEdge].tSt=dest->ebData[nEdge].tEn=tID;
+ }
+ }
+ }
+ else if (join == join_round)
+ {
+ const Geom::Point sx = pos - width * pnor;
+ rightStNo = dest->AddPoint (sx);
+ const Geom::Point ex = pos - width * nnor;
+ rightEnNo = dest->AddPoint (ex);
+
+ RecRound(dest, rightStNo, rightEnNo,
+ sx, ex, -pnor, -nnor ,pos, width);
+ }
+ else
+ {
+ rightStNo = dest->AddPoint (pos - width * pnor);
+ rightEnNo = dest->AddPoint (pos - width * nnor);
+ int nEdge=dest->AddEdge (rightStNo, rightEnNo);
+ if ( dest->hasBackData() ) {
+ dest->ebData[nEdge].pathID=pathID;
+ dest->ebData[nEdge].pieceID=pieceID;
+ dest->ebData[nEdge].tSt=dest->ebData[nEdge].tEn=tID;
+ }
+ }
+ }
+ else
+ {
+ /* Geom::Point biss;
+ biss=next.x-prev.x;
+ biss.y=next.y-prev.y;
+ double c2=cross(biss, next);
+ double l=width/c2;
+ double projn=l*(dot(biss,next));
+ double projp=-l*(dot(biss,prev));
+ if ( projp <= 0.5*prevL && projn <= 0.5*nextL ) {
+ double x,y;
+ x=pos.x+l*biss.x;
+ y=pos.y+l*biss.y;
+ rightEnNo=rightStNo=dest->AddPoint(x,y);
+ } else {*/
+ rightStNo = dest->AddPoint (pos - width*pnor);
+ rightEnNo = dest->AddPoint (pos - width*nnor);
+// int midNo = dest->AddPoint (pos);
+// int nEdge=dest->AddEdge (rightStNo, midNo);
+ int nEdge=dest->AddEdge (rightStNo, rightEnNo);
+ if ( dest->hasBackData() ) {
+ dest->ebData[nEdge].pathID=pathID;
+ dest->ebData[nEdge].pieceID=pieceID;
+ dest->ebData[nEdge].tSt=dest->ebData[nEdge].tEn=tID;
+ }
+// nEdge=dest->AddEdge (midNo, rightEnNo);
+// if ( dest->hasBackData() ) {
+// dest->ebData[nEdge].pathID=pathID;
+// dest->ebData[nEdge].pieceID=pieceID;
+// dest->ebData[nEdge].tSt=dest->ebData[nEdge].tEn=tID;
+// }
+ // }
+ }
+}
+
+
+// a very ugly way to produce round joins: doing one (or two, depend on the angle of the join) quadratic bezier curves
+// but since most joins are going to be small, nobody will notice -- but somebody noticed and now the ugly stuff is gone! so:
+
+// a very nice way to produce round joins, caps or dots
+void Path::RecRound(Shape *dest, int sNo, int eNo, // start and end index
+ Geom::Point const &iS, Geom::Point const &iE, // start and end point
+ Geom::Point const &nS, Geom::Point const &nE, // start and end normal vector
+ Geom::Point &origine, float width) // center and radius of round
+{
+ //Geom::Point diff = iS - iE;
+ //double dist = dot(diff, diff);
+ if (width < 0.5 || dot(iS - iE, iS - iE)/width < 2.0) {
+ dest->AddEdge(sNo, eNo);
+ return;
+ }
+ double ang, sia, lod;
+ if (nS == -nE) {
+ ang = M_PI;
+ sia = 1;
+ } else {
+ double coa = dot(nS, nE);
+ sia = cross(nE, nS);
+ ang = acos(coa);
+ if ( coa >= 1 ) {
+ ang = 0;
+ }
+ if ( coa <= -1 ) {
+ ang = M_PI;
+ }
+ }
+ lod = 0.02 + 10 / (10 + width); // limit detail to about 2 degrees (180 * 0.02/Pi degrees)
+ ang /= lod;
+
+ int nbS = (int) floor(ang);
+ Geom::Rotate omega(((sia > 0) ? -lod : lod));
+ Geom::Point cur = iS - origine;
+ // StrokeNormalize(cur);
+ // cur*=width;
+ int lastNo = sNo;
+ for (int i = 0; i < nbS; i++) {
+ cur = cur * omega;
+ Geom::Point m = origine + cur;
+ int mNo = dest->AddPoint(m);
+ dest->AddEdge(lastNo, mNo);
+ lastNo = mNo;
+ }
+ dest->AddEdge(lastNo, eNo);
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/livarot/README b/src/livarot/README
new file mode 100644
index 0000000..3e6f138
--- /dev/null
+++ b/src/livarot/README
@@ -0,0 +1,17 @@
+
+
+This directory contains path and shape related code. This code is
+partially replaced by lib2geom. We still rely on this code for:
+
+* Binary path operations.
+* Path offsetting.
+* Finding start and end positions for lines text inside a shape.
+
+To do:
+
+* Move needed functionality to lib2geom or independent functions.
+* Delete directory.
+
+(Livarot is a pungent French cow milk cheese.)
+
+
diff --git a/src/livarot/Shape.cpp b/src/livarot/Shape.cpp
new file mode 100644
index 0000000..0702534
--- /dev/null
+++ b/src/livarot/Shape.cpp
@@ -0,0 +1,2347 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * TODO: insert short description here
+ *//*
+ * Authors:
+ * see git history
+ * Fred
+ *
+ * Copyright (C) 2018 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <cstdio>
+#include <cstdlib>
+#include <glib.h>
+#include "Shape.h"
+#include "livarot/sweep-event-queue.h"
+#include "livarot/sweep-tree-list.h"
+
+/*
+ * Shape instances handling.
+ * never (i repeat: never) modify edges and points links; use Connect() and Disconnect() instead
+ * the graph is stored as a set of points and edges, with edges in a doubly-linked list for each point.
+ */
+
+Shape::Shape()
+ : nbQRas(0),
+ firstQRas(-1),
+ lastQRas(-1),
+ qrsData(nullptr),
+ nbInc(0),
+ maxInc(0),
+ iData(nullptr),
+ sTree(nullptr),
+ sEvts(nullptr),
+ _need_points_sorting(false),
+ _need_edges_sorting(false),
+ _has_points_data(false),
+ _point_data_initialised(false),
+ _has_edges_data(false),
+ _has_sweep_src_data(false),
+ _has_sweep_dest_data(false),
+ _has_raster_data(false),
+ _has_quick_raster_data(false),
+ _has_back_data(false),
+ _has_voronoi_data(false),
+ _bbox_up_to_date(false)
+{
+ leftX = topY = rightX = bottomY = 0;
+ maxPt = 0;
+ maxAr = 0;
+
+ type = shape_polygon;
+}
+Shape::~Shape ()
+{
+ maxPt = 0;
+ maxAr = 0;
+ free(qrsData);
+}
+
+void Shape::Affiche()
+{
+ printf("sh=%p nbPt=%i nbAr=%i\n", this, static_cast<int>(_pts.size()), static_cast<int>(_aretes.size())); // localizing ok
+ for (unsigned int i=0; i<_pts.size(); i++) {
+ printf("pt %u : x=(%f %f) dI=%i dO=%i\n",i, _pts[i].x[0], _pts[i].x[1], _pts[i].dI, _pts[i].dO); // localizing ok
+ }
+ for (unsigned int i=0; i<_aretes.size(); i++) {
+ printf("ar %u : dx=(%f %f) st=%i en=%i\n",i, _aretes[i].dx[0], _aretes[i].dx[1], _aretes[i].st, _aretes[i].en); // localizing ok
+ }
+}
+
+/**
+ * Allocates space for point cache or clears the cache
+ \param nVal Allocate a cache (true) or clear it (false)
+ */
+void
+Shape::MakePointData (bool nVal)
+{
+ if (nVal)
+ {
+ if (_has_points_data == false)
+ {
+ _has_points_data = true;
+ _point_data_initialised = false;
+ _bbox_up_to_date = false;
+ pData.resize(maxPt);
+ }
+ }
+ /* no need to clean point data - keep it cached*/
+}
+
+void
+Shape::MakeEdgeData (bool nVal)
+{
+ if (nVal)
+ {
+ if (_has_edges_data == false)
+ {
+ _has_edges_data = true;
+ eData.resize(maxAr);
+ }
+ }
+ else
+ {
+ if (_has_edges_data)
+ {
+ _has_edges_data = false;
+ eData.clear();
+ }
+ }
+}
+
+void
+Shape::MakeRasterData (bool nVal)
+{
+ if (nVal)
+ {
+ if (_has_raster_data == false)
+ {
+ _has_raster_data = true;
+ swrData.resize(maxAr);
+ }
+ }
+ else
+ {
+ if (_has_raster_data)
+ {
+ _has_raster_data = false;
+ swrData.clear();
+ }
+ }
+}
+void
+Shape::MakeQuickRasterData (bool nVal)
+{
+ if (nVal)
+ {
+ if (_has_quick_raster_data == false)
+ {
+ _has_quick_raster_data = true;
+ quick_raster_data* new_qrsData = static_cast<quick_raster_data*>(realloc(qrsData, maxAr * sizeof(quick_raster_data)));
+ if (!new_qrsData) {
+ g_error("Not enough memory available for reallocating Shape::qrsData");
+ } else {
+ qrsData = new_qrsData;
+ }
+ }
+ }
+ else
+ {
+ if (_has_quick_raster_data)
+ {
+ _has_quick_raster_data = false;
+ }
+ }
+}
+void
+Shape::MakeSweepSrcData (bool nVal)
+{
+ if (nVal)
+ {
+ if (_has_sweep_src_data == false)
+ {
+ _has_sweep_src_data = true;
+ swsData.resize(maxAr);
+ }
+ }
+ else
+ {
+ if (_has_sweep_src_data)
+ {
+ _has_sweep_src_data = false;
+ swsData.clear();
+ }
+ }
+}
+void
+Shape::MakeSweepDestData (bool nVal)
+{
+ if (nVal)
+ {
+ if (_has_sweep_dest_data == false)
+ {
+ _has_sweep_dest_data = true;
+ swdData.resize(maxAr);
+ }
+ }
+ else
+ {
+ if (_has_sweep_dest_data)
+ {
+ _has_sweep_dest_data = false;
+ swdData.clear();
+ }
+ }
+}
+void
+Shape::MakeBackData (bool nVal)
+{
+ if (nVal)
+ {
+ if (_has_back_data == false)
+ {
+ _has_back_data = true;
+ ebData.resize(maxAr);
+ }
+ }
+ else
+ {
+ if (_has_back_data)
+ {
+ _has_back_data = false;
+ ebData.clear();
+ }
+ }
+}
+void
+Shape::MakeVoronoiData (bool nVal)
+{
+ if (nVal)
+ {
+ if (_has_voronoi_data == false)
+ {
+ _has_voronoi_data = true;
+ vorpData.resize(maxPt);
+ voreData.resize(maxAr);
+ }
+ }
+ else
+ {
+ if (_has_voronoi_data)
+ {
+ _has_voronoi_data = false;
+ vorpData.clear();
+ voreData.clear();
+ }
+ }
+}
+
+
+/**
+ * Copy point and edge data from `who' into this object, discarding
+ * any cached data that we have.
+ */
+void
+Shape::Copy (Shape * who)
+{
+ if (who == nullptr)
+ {
+ Reset (0, 0);
+ return;
+ }
+ MakePointData (false);
+ MakeEdgeData (false);
+ MakeSweepSrcData (false);
+ MakeSweepDestData (false);
+ MakeRasterData (false);
+ MakeQuickRasterData (false);
+ MakeBackData (false);
+
+ delete sTree;
+ sTree = nullptr;
+ delete sEvts;
+ sEvts = nullptr;
+
+ Reset (who->numberOfPoints(), who->numberOfEdges());
+ type = who->type;
+ _need_points_sorting = who->_need_points_sorting;
+ _need_edges_sorting = who->_need_edges_sorting;
+ _has_points_data = false;
+ _point_data_initialised = false;
+ _has_edges_data = false;
+ _has_sweep_src_data = false;
+ _has_sweep_dest_data = false;
+ _has_raster_data = false;
+ _has_quick_raster_data = false;
+ _has_back_data = false;
+ _has_voronoi_data = false;
+ _bbox_up_to_date = false;
+
+ _pts = who->_pts;
+ _aretes = who->_aretes;
+}
+
+/**
+ * Clear points and edges and prepare internal data using new size.
+ */
+void
+Shape::Reset (int pointCount, int edgeCount)
+{
+ _pts.clear();
+ _aretes.clear();
+
+ type = shape_polygon;
+ if (pointCount > maxPt)
+ {
+ maxPt = pointCount;
+ if (_has_points_data)
+ pData.resize(maxPt);
+ if (_has_voronoi_data)
+ vorpData.resize(maxPt);
+ }
+ if (edgeCount > maxAr)
+ {
+ maxAr = edgeCount;
+ if (_has_edges_data)
+ eData.resize(maxAr);
+ if (_has_sweep_dest_data)
+ swdData.resize(maxAr);
+ if (_has_sweep_src_data)
+ swsData.resize(maxAr);
+ if (_has_back_data)
+ ebData.resize(maxAr);
+ if (_has_voronoi_data)
+ voreData.resize(maxAr);
+ }
+ _need_points_sorting = false;
+ _need_edges_sorting = false;
+ _point_data_initialised = false;
+ _bbox_up_to_date = false;
+}
+
+int
+Shape::AddPoint (const Geom::Point x)
+{
+ if (numberOfPoints() >= maxPt)
+ {
+ maxPt = 2 * numberOfPoints() + 1;
+ if (_has_points_data)
+ pData.resize(maxPt);
+ if (_has_voronoi_data)
+ vorpData.resize(maxPt);
+ }
+
+ dg_point p;
+ p.x = x;
+ p.dI = p.dO = 0;
+ p.incidentEdge[FIRST] = p.incidentEdge[LAST] = -1;
+ p.oldDegree = -1;
+ _pts.push_back(p);
+ int const n = _pts.size() - 1;
+
+ if (_has_points_data)
+ {
+ pData[n].pending = 0;
+ pData[n].edgeOnLeft = -1;
+ pData[n].nextLinkedPoint = -1;
+ pData[n].askForWindingS = nullptr;
+ pData[n].askForWindingB = -1;
+ pData[n].rx[0] = Round(p.x[0]);
+ pData[n].rx[1] = Round(p.x[1]);
+ }
+ if (_has_voronoi_data)
+ {
+ vorpData[n].value = 0.0;
+ vorpData[n].winding = -2;
+ }
+ _need_points_sorting = true;
+
+ return n;
+}
+
+void
+Shape::SubPoint (int p)
+{
+ if (p < 0 || p >= numberOfPoints())
+ return;
+ _need_points_sorting = true;
+ int cb;
+ cb = getPoint(p).incidentEdge[FIRST];
+ while (cb >= 0 && cb < numberOfEdges())
+ {
+ if (getEdge(cb).st == p)
+ {
+ int ncb = getEdge(cb).nextS;
+ _aretes[cb].nextS = _aretes[cb].prevS = -1;
+ _aretes[cb].st = -1;
+ cb = ncb;
+ }
+ else if (getEdge(cb).en == p)
+ {
+ int ncb = getEdge(cb).nextE;
+ _aretes[cb].nextE = _aretes[cb].prevE = -1;
+ _aretes[cb].en = -1;
+ cb = ncb;
+ }
+ else
+ {
+ break;
+ }
+ }
+ _pts[p].incidentEdge[FIRST] = _pts[p].incidentEdge[LAST] = -1;
+ if (p < numberOfPoints() - 1)
+ SwapPoints (p, numberOfPoints() - 1);
+ _pts.pop_back();
+}
+
+void
+Shape::SwapPoints (int a, int b)
+{
+ if (a == b)
+ return;
+ if (getPoint(a).totalDegree() == 2 && getPoint(b).totalDegree() == 2)
+ {
+ int cb = getPoint(a).incidentEdge[FIRST];
+ if (getEdge(cb).st == a)
+ {
+ _aretes[cb].st = numberOfPoints();
+ }
+ else if (getEdge(cb).en == a)
+ {
+ _aretes[cb].en = numberOfPoints();
+ }
+ cb = getPoint(a).incidentEdge[LAST];
+ if (getEdge(cb).st == a)
+ {
+ _aretes[cb].st = numberOfPoints();
+ }
+ else if (getEdge(cb).en == a)
+ {
+ _aretes[cb].en = numberOfPoints();
+ }
+
+ cb = getPoint(b).incidentEdge[FIRST];
+ if (getEdge(cb).st == b)
+ {
+ _aretes[cb].st = a;
+ }
+ else if (getEdge(cb).en == b)
+ {
+ _aretes[cb].en = a;
+ }
+ cb = getPoint(b).incidentEdge[LAST];
+ if (getEdge(cb).st == b)
+ {
+ _aretes[cb].st = a;
+ }
+ else if (getEdge(cb).en == b)
+ {
+ _aretes[cb].en = a;
+ }
+
+ cb = getPoint(a).incidentEdge[FIRST];
+ if (getEdge(cb).st == numberOfPoints())
+ {
+ _aretes[cb].st = b;
+ }
+ else if (getEdge(cb).en == numberOfPoints())
+ {
+ _aretes[cb].en = b;
+ }
+ cb = getPoint(a).incidentEdge[LAST];
+ if (getEdge(cb).st == numberOfPoints())
+ {
+ _aretes[cb].st = b;
+ }
+ else if (getEdge(cb).en == numberOfPoints())
+ {
+ _aretes[cb].en = b;
+ }
+
+ }
+ else
+ {
+ int cb;
+ cb = getPoint(a).incidentEdge[FIRST];
+ while (cb >= 0)
+ {
+ int ncb = NextAt (a, cb);
+ if (getEdge(cb).st == a)
+ {
+ _aretes[cb].st = numberOfPoints();
+ }
+ else if (getEdge(cb).en == a)
+ {
+ _aretes[cb].en = numberOfPoints();
+ }
+ cb = ncb;
+ }
+ cb = getPoint(b).incidentEdge[FIRST];
+ while (cb >= 0)
+ {
+ int ncb = NextAt (b, cb);
+ if (getEdge(cb).st == b)
+ {
+ _aretes[cb].st = a;
+ }
+ else if (getEdge(cb).en == b)
+ {
+ _aretes[cb].en = a;
+ }
+ cb = ncb;
+ }
+ cb = getPoint(a).incidentEdge[FIRST];
+ while (cb >= 0)
+ {
+ int ncb = NextAt (numberOfPoints(), cb);
+ if (getEdge(cb).st == numberOfPoints())
+ {
+ _aretes[cb].st = b;
+ }
+ else if (getEdge(cb).en == numberOfPoints())
+ {
+ _aretes[cb].en = b;
+ }
+ cb = ncb;
+ }
+ }
+ {
+ dg_point swap = getPoint(a);
+ _pts[a] = getPoint(b);
+ _pts[b] = swap;
+ }
+ if (_has_points_data)
+ {
+ point_data swad = pData[a];
+ pData[a] = pData[b];
+ pData[b] = swad;
+ // pData[pData[a].oldInd].newInd=a;
+ // pData[pData[b].oldInd].newInd=b;
+ }
+ if (_has_voronoi_data)
+ {
+ voronoi_point swav = vorpData[a];
+ vorpData[a] = vorpData[b];
+ vorpData[b] = swav;
+ }
+}
+void
+Shape::SwapPoints (int a, int b, int c)
+{
+ if (a == b || b == c || a == c)
+ return;
+ SwapPoints (a, b);
+ SwapPoints (b, c);
+}
+
+void
+Shape::SortPoints ()
+{
+ if (_need_points_sorting && hasPoints())
+ SortPoints (0, numberOfPoints() - 1);
+ _need_points_sorting = false;
+}
+
+void
+Shape::SortPointsRounded ()
+{
+ if (hasPoints())
+ SortPointsRounded (0, numberOfPoints() - 1);
+}
+
+void
+Shape::SortPoints (int s, int e)
+{
+ if (s >= e)
+ return;
+ if (e == s + 1)
+ {
+ if (getPoint(s).x[1] > getPoint(e).x[1]
+ || (getPoint(s).x[1] == getPoint(e).x[1] && getPoint(s).x[0] > getPoint(e).x[0]))
+ SwapPoints (s, e);
+ return;
+ }
+
+ int ppos = (s + e) / 2;
+ int plast = ppos;
+ double pvalx = getPoint(ppos).x[0];
+ double pvaly = getPoint(ppos).x[1];
+
+ int le = s, ri = e;
+ while (le < ppos || ri > plast)
+ {
+ if (le < ppos)
+ {
+ do
+ {
+ int test = 0;
+ if (getPoint(le).x[1] > pvaly)
+ {
+ test = 1;
+ }
+ else if (getPoint(le).x[1] == pvaly)
+ {
+ if (getPoint(le).x[0] > pvalx)
+ {
+ test = 1;
+ }
+ else if (getPoint(le).x[0] == pvalx)
+ {
+ test = 0;
+ }
+ else
+ {
+ test = -1;
+ }
+ }
+ else
+ {
+ test = -1;
+ }
+ if (test == 0)
+ {
+ // on colle les valeurs egales au pivot ensemble
+ if (le < ppos - 1)
+ {
+ SwapPoints (le, ppos - 1, ppos);
+ ppos--;
+ continue; // sans changer le
+ }
+ else if (le == ppos - 1)
+ {
+ ppos--;
+ break;
+ }
+ else
+ {
+ // oupsie
+ break;
+ }
+ }
+ if (test > 0)
+ {
+ break;
+ }
+ le++;
+ }
+ while (le < ppos);
+ }
+ if (ri > plast)
+ {
+ do
+ {
+ int test = 0;
+ if (getPoint(ri).x[1] > pvaly)
+ {
+ test = 1;
+ }
+ else if (getPoint(ri).x[1] == pvaly)
+ {
+ if (getPoint(ri).x[0] > pvalx)
+ {
+ test = 1;
+ }
+ else if (getPoint(ri).x[0] == pvalx)
+ {
+ test = 0;
+ }
+ else
+ {
+ test = -1;
+ }
+ }
+ else
+ {
+ test = -1;
+ }
+ if (test == 0)
+ {
+ // on colle les valeurs egales au pivot ensemble
+ if (ri > plast + 1)
+ {
+ SwapPoints (ri, plast + 1, plast);
+ plast++;
+ continue; // sans changer ri
+ }
+ else if (ri == plast + 1)
+ {
+ plast++;
+ break;
+ }
+ else
+ {
+ // oupsie
+ break;
+ }
+ }
+ if (test < 0)
+ {
+ break;
+ }
+ ri--;
+ }
+ while (ri > plast);
+ }
+ if (le < ppos)
+ {
+ if (ri > plast)
+ {
+ SwapPoints (le, ri);
+ le++;
+ ri--;
+ }
+ else
+ {
+ if (le < ppos - 1)
+ {
+ SwapPoints (ppos - 1, plast, le);
+ ppos--;
+ plast--;
+ }
+ else if (le == ppos - 1)
+ {
+ SwapPoints (plast, le);
+ ppos--;
+ plast--;
+ }
+ }
+ }
+ else
+ {
+ if (ri > plast + 1)
+ {
+ SwapPoints (plast + 1, ppos, ri);
+ ppos++;
+ plast++;
+ }
+ else if (ri == plast + 1)
+ {
+ SwapPoints (ppos, ri);
+ ppos++;
+ plast++;
+ }
+ else
+ {
+ break;
+ }
+ }
+ }
+ SortPoints (s, ppos - 1);
+ SortPoints (plast + 1, e);
+}
+
+void
+Shape::SortPointsByOldInd (int s, int e)
+{
+ if (s >= e)
+ return;
+ if (e == s + 1)
+ {
+ if (getPoint(s).x[1] > getPoint(e).x[1] || (getPoint(s).x[1] == getPoint(e).x[1] && getPoint(s).x[0] > getPoint(e).x[0])
+ || (getPoint(s).x[1] == getPoint(e).x[1] && getPoint(s).x[0] == getPoint(e).x[0]
+ && pData[s].oldInd > pData[e].oldInd))
+ SwapPoints (s, e);
+ return;
+ }
+
+ int ppos = (s + e) / 2;
+ int plast = ppos;
+ double pvalx = getPoint(ppos).x[0];
+ double pvaly = getPoint(ppos).x[1];
+ int pvali = pData[ppos].oldInd;
+
+ int le = s, ri = e;
+ while (le < ppos || ri > plast)
+ {
+ if (le < ppos)
+ {
+ do
+ {
+ int test = 0;
+ if (getPoint(le).x[1] > pvaly)
+ {
+ test = 1;
+ }
+ else if (getPoint(le).x[1] == pvaly)
+ {
+ if (getPoint(le).x[0] > pvalx)
+ {
+ test = 1;
+ }
+ else if (getPoint(le).x[0] == pvalx)
+ {
+ if (pData[le].oldInd > pvali)
+ {
+ test = 1;
+ }
+ else if (pData[le].oldInd == pvali)
+ {
+ test = 0;
+ }
+ else
+ {
+ test = -1;
+ }
+ }
+ else
+ {
+ test = -1;
+ }
+ }
+ else
+ {
+ test = -1;
+ }
+ if (test == 0)
+ {
+ // on colle les valeurs egales au pivot ensemble
+ if (le < ppos - 1)
+ {
+ SwapPoints (le, ppos - 1, ppos);
+ ppos--;
+ continue; // sans changer le
+ }
+ else if (le == ppos - 1)
+ {
+ ppos--;
+ break;
+ }
+ else
+ {
+ // oupsie
+ break;
+ }
+ }
+ if (test > 0)
+ {
+ break;
+ }
+ le++;
+ }
+ while (le < ppos);
+ }
+ if (ri > plast)
+ {
+ do
+ {
+ int test = 0;
+ if (getPoint(ri).x[1] > pvaly)
+ {
+ test = 1;
+ }
+ else if (getPoint(ri).x[1] == pvaly)
+ {
+ if (getPoint(ri).x[0] > pvalx)
+ {
+ test = 1;
+ }
+ else if (getPoint(ri).x[0] == pvalx)
+ {
+ if (pData[ri].oldInd > pvali)
+ {
+ test = 1;
+ }
+ else if (pData[ri].oldInd == pvali)
+ {
+ test = 0;
+ }
+ else
+ {
+ test = -1;
+ }
+ }
+ else
+ {
+ test = -1;
+ }
+ }
+ else
+ {
+ test = -1;
+ }
+ if (test == 0)
+ {
+ // on colle les valeurs egales au pivot ensemble
+ if (ri > plast + 1)
+ {
+ SwapPoints (ri, plast + 1, plast);
+ plast++;
+ continue; // sans changer ri
+ }
+ else if (ri == plast + 1)
+ {
+ plast++;
+ break;
+ }
+ else
+ {
+ // oupsie
+ break;
+ }
+ }
+ if (test < 0)
+ {
+ break;
+ }
+ ri--;
+ }
+ while (ri > plast);
+ }
+ if (le < ppos)
+ {
+ if (ri > plast)
+ {
+ SwapPoints (le, ri);
+ le++;
+ ri--;
+ }
+ else
+ {
+ if (le < ppos - 1)
+ {
+ SwapPoints (ppos - 1, plast, le);
+ ppos--;
+ plast--;
+ }
+ else if (le == ppos - 1)
+ {
+ SwapPoints (plast, le);
+ ppos--;
+ plast--;
+ }
+ }
+ }
+ else
+ {
+ if (ri > plast + 1)
+ {
+ SwapPoints (plast + 1, ppos, ri);
+ ppos++;
+ plast++;
+ }
+ else if (ri == plast + 1)
+ {
+ SwapPoints (ppos, ri);
+ ppos++;
+ plast++;
+ }
+ else
+ {
+ break;
+ }
+ }
+ }
+ SortPointsByOldInd (s, ppos - 1);
+ SortPointsByOldInd (plast + 1, e);
+}
+
+void
+Shape::SortPointsRounded (int s, int e)
+{
+ if (s >= e)
+ return;
+ if (e == s + 1)
+ {
+ if (pData[s].rx[1] > pData[e].rx[1]
+ || (pData[s].rx[1] == pData[e].rx[1] && pData[s].rx[0] > pData[e].rx[0]))
+ SwapPoints (s, e);
+ return;
+ }
+
+ int ppos = (s + e) / 2;
+ int plast = ppos;
+ double pvalx = pData[ppos].rx[0];
+ double pvaly = pData[ppos].rx[1];
+
+ int le = s, ri = e;
+ while (le < ppos || ri > plast)
+ {
+ if (le < ppos)
+ {
+ do
+ {
+ int test = 0;
+ if (pData[le].rx[1] > pvaly)
+ {
+ test = 1;
+ }
+ else if (pData[le].rx[1] == pvaly)
+ {
+ if (pData[le].rx[0] > pvalx)
+ {
+ test = 1;
+ }
+ else if (pData[le].rx[0] == pvalx)
+ {
+ test = 0;
+ }
+ else
+ {
+ test = -1;
+ }
+ }
+ else
+ {
+ test = -1;
+ }
+ if (test == 0)
+ {
+ // on colle les valeurs egales au pivot ensemble
+ if (le < ppos - 1)
+ {
+ SwapPoints (le, ppos - 1, ppos);
+ ppos--;
+ continue; // sans changer le
+ }
+ else if (le == ppos - 1)
+ {
+ ppos--;
+ break;
+ }
+ else
+ {
+ // oupsie
+ break;
+ }
+ }
+ if (test > 0)
+ {
+ break;
+ }
+ le++;
+ }
+ while (le < ppos);
+ }
+ if (ri > plast)
+ {
+ do
+ {
+ int test = 0;
+ if (pData[ri].rx[1] > pvaly)
+ {
+ test = 1;
+ }
+ else if (pData[ri].rx[1] == pvaly)
+ {
+ if (pData[ri].rx[0] > pvalx)
+ {
+ test = 1;
+ }
+ else if (pData[ri].rx[0] == pvalx)
+ {
+ test = 0;
+ }
+ else
+ {
+ test = -1;
+ }
+ }
+ else
+ {
+ test = -1;
+ }
+ if (test == 0)
+ {
+ // on colle les valeurs egales au pivot ensemble
+ if (ri > plast + 1)
+ {
+ SwapPoints (ri, plast + 1, plast);
+ plast++;
+ continue; // sans changer ri
+ }
+ else if (ri == plast + 1)
+ {
+ plast++;
+ break;
+ }
+ else
+ {
+ // oupsie
+ break;
+ }
+ }
+ if (test < 0)
+ {
+ break;
+ }
+ ri--;
+ }
+ while (ri > plast);
+ }
+ if (le < ppos)
+ {
+ if (ri > plast)
+ {
+ SwapPoints (le, ri);
+ le++;
+ ri--;
+ }
+ else
+ {
+ if (le < ppos - 1)
+ {
+ SwapPoints (ppos - 1, plast, le);
+ ppos--;
+ plast--;
+ }
+ else if (le == ppos - 1)
+ {
+ SwapPoints (plast, le);
+ ppos--;
+ plast--;
+ }
+ }
+ }
+ else
+ {
+ if (ri > plast + 1)
+ {
+ SwapPoints (plast + 1, ppos, ri);
+ ppos++;
+ plast++;
+ }
+ else if (ri == plast + 1)
+ {
+ SwapPoints (ppos, ri);
+ ppos++;
+ plast++;
+ }
+ else
+ {
+ break;
+ }
+ }
+ }
+ SortPointsRounded (s, ppos - 1);
+ SortPointsRounded (plast + 1, e);
+}
+
+/*
+ *
+ */
+int
+Shape::AddEdge (int st, int en)
+{
+ if (st == en)
+ return -1;
+ if (st < 0 || en < 0)
+ return -1;
+ type = shape_graph;
+ if (numberOfEdges() >= maxAr)
+ {
+ maxAr = 2 * numberOfEdges() + 1;
+ if (_has_edges_data)
+ eData.resize(maxAr);
+ if (_has_sweep_src_data)
+ swsData.resize(maxAr);
+ if (_has_sweep_dest_data)
+ swdData.resize(maxAr);
+ if (_has_raster_data)
+ swrData.resize(maxAr);
+ if (_has_back_data)
+ ebData.resize(maxAr);
+ if (_has_voronoi_data)
+ voreData.resize(maxAr);
+ }
+
+ dg_arete a;
+ a.dx = Geom::Point(0, 0);
+ a.st = a.en = -1;
+ a.prevS = a.nextS = -1;
+ a.prevE = a.nextE = -1;
+ if (st >= 0 && en >= 0) {
+ a.dx = getPoint(en).x - getPoint(st).x;
+ }
+
+ _aretes.push_back(a);
+ int const n = numberOfEdges() - 1;
+
+ ConnectStart (st, n);
+ ConnectEnd (en, n);
+ if (_has_edges_data)
+ {
+ eData[n].weight = 1;
+ eData[n].rdx = getEdge(n).dx;
+ }
+ if (_has_sweep_src_data)
+ {
+ swsData[n].misc = nullptr;
+ swsData[n].firstLinkedPoint = -1;
+ }
+ if (_has_back_data)
+ {
+ ebData[n].pathID = -1;
+ ebData[n].pieceID = -1;
+ ebData[n].tSt = ebData[n].tEn = 0;
+ }
+ if (_has_voronoi_data)
+ {
+ voreData[n].leF = -1;
+ voreData[n].riF = -1;
+ }
+ _need_edges_sorting = true;
+ return n;
+}
+
+int
+Shape::AddEdge (int st, int en, int leF, int riF)
+{
+ if (st == en)
+ return -1;
+ if (st < 0 || en < 0)
+ return -1;
+ {
+ int cb = getPoint(st).incidentEdge[FIRST];
+ while (cb >= 0)
+ {
+ if (getEdge(cb).st == st && getEdge(cb).en == en)
+ return -1; // doublon
+ if (getEdge(cb).st == en && getEdge(cb).en == st)
+ return -1; // doublon
+ cb = NextAt (st, cb);
+ }
+ }
+ type = shape_graph;
+ if (numberOfEdges() >= maxAr)
+ {
+ maxAr = 2 * numberOfEdges() + 1;
+ if (_has_edges_data)
+ eData.resize(maxAr);
+ if (_has_sweep_src_data)
+ swsData.resize(maxAr);
+ if (_has_sweep_dest_data)
+ swdData.resize(maxAr);
+ if (_has_raster_data)
+ swrData.resize(maxAr);
+ if (_has_back_data)
+ ebData.resize(maxAr);
+ if (_has_voronoi_data)
+ voreData.resize(maxAr);
+ }
+
+ dg_arete a;
+ a.dx = Geom::Point(0, 0);
+ a.st = a.en = -1;
+ a.prevS = a.nextS = -1;
+ a.prevE = a.nextE = -1;
+ if (st >= 0 && en >= 0) {
+ a.dx = getPoint(en).x - getPoint(st).x;
+ }
+
+ _aretes.push_back(a);
+ int const n = numberOfEdges() - 1;
+
+ ConnectStart (st, n);
+ ConnectEnd (en, n);
+ if (_has_edges_data)
+ {
+ eData[n].weight = 1;
+ eData[n].rdx = getEdge(n).dx;
+ }
+ if (_has_sweep_src_data)
+ {
+ swsData[n].misc = nullptr;
+ swsData[n].firstLinkedPoint = -1;
+ }
+ if (_has_back_data)
+ {
+ ebData[n].pathID = -1;
+ ebData[n].pieceID = -1;
+ ebData[n].tSt = ebData[n].tEn = 0;
+ }
+ if (_has_voronoi_data)
+ {
+ voreData[n].leF = leF;
+ voreData[n].riF = riF;
+ }
+ _need_edges_sorting = true;
+ return n;
+}
+
+void
+Shape::SubEdge (int e)
+{
+ if (e < 0 || e >= numberOfEdges())
+ return;
+ type = shape_graph;
+ DisconnectStart (e);
+ DisconnectEnd (e);
+ if (e < numberOfEdges() - 1)
+ SwapEdges (e, numberOfEdges() - 1);
+ _aretes.pop_back();
+ _need_edges_sorting = true;
+}
+
+void
+Shape::SwapEdges (int a, int b)
+{
+ if (a == b)
+ return;
+ if (getEdge(a).prevS >= 0 && getEdge(a).prevS != b)
+ {
+ if (getEdge(getEdge(a).prevS).st == getEdge(a).st)
+ {
+ _aretes[getEdge(a).prevS].nextS = b;
+ }
+ else if (getEdge(getEdge(a).prevS).en == getEdge(a).st)
+ {
+ _aretes[getEdge(a).prevS].nextE = b;
+ }
+ }
+ if (getEdge(a).nextS >= 0 && getEdge(a).nextS != b)
+ {
+ if (getEdge(getEdge(a).nextS).st == getEdge(a).st)
+ {
+ _aretes[getEdge(a).nextS].prevS = b;
+ }
+ else if (getEdge(getEdge(a).nextS).en == getEdge(a).st)
+ {
+ _aretes[getEdge(a).nextS].prevE = b;
+ }
+ }
+ if (getEdge(a).prevE >= 0 && getEdge(a).prevE != b)
+ {
+ if (getEdge(getEdge(a).prevE).st == getEdge(a).en)
+ {
+ _aretes[getEdge(a).prevE].nextS = b;
+ }
+ else if (getEdge(getEdge(a).prevE).en == getEdge(a).en)
+ {
+ _aretes[getEdge(a).prevE].nextE = b;
+ }
+ }
+ if (getEdge(a).nextE >= 0 && getEdge(a).nextE != b)
+ {
+ if (getEdge(getEdge(a).nextE).st == getEdge(a).en)
+ {
+ _aretes[getEdge(a).nextE].prevS = b;
+ }
+ else if (getEdge(getEdge(a).nextE).en == getEdge(a).en)
+ {
+ _aretes[getEdge(a).nextE].prevE = b;
+ }
+ }
+ if (getEdge(a).st >= 0)
+ {
+ if (getPoint(getEdge(a).st).incidentEdge[FIRST] == a)
+ _pts[getEdge(a).st].incidentEdge[FIRST] = numberOfEdges();
+ if (getPoint(getEdge(a).st).incidentEdge[LAST] == a)
+ _pts[getEdge(a).st].incidentEdge[LAST] = numberOfEdges();
+ }
+ if (getEdge(a).en >= 0)
+ {
+ if (getPoint(getEdge(a).en).incidentEdge[FIRST] == a)
+ _pts[getEdge(a).en].incidentEdge[FIRST] = numberOfEdges();
+ if (getPoint(getEdge(a).en).incidentEdge[LAST] == a)
+ _pts[getEdge(a).en].incidentEdge[LAST] = numberOfEdges();
+ }
+
+
+ if (getEdge(b).prevS >= 0 && getEdge(b).prevS != a)
+ {
+ if (getEdge(getEdge(b).prevS).st == getEdge(b).st)
+ {
+ _aretes[getEdge(b).prevS].nextS = a;
+ }
+ else if (getEdge(getEdge(b).prevS).en == getEdge(b).st)
+ {
+ _aretes[getEdge(b).prevS].nextE = a;
+ }
+ }
+ if (getEdge(b).nextS >= 0 && getEdge(b).nextS != a)
+ {
+ if (getEdge(getEdge(b).nextS).st == getEdge(b).st)
+ {
+ _aretes[getEdge(b).nextS].prevS = a;
+ }
+ else if (getEdge(getEdge(b).nextS).en == getEdge(b).st)
+ {
+ _aretes[getEdge(b).nextS].prevE = a;
+ }
+ }
+ if (getEdge(b).prevE >= 0 && getEdge(b).prevE != a)
+ {
+ if (getEdge(getEdge(b).prevE).st == getEdge(b).en)
+ {
+ _aretes[getEdge(b).prevE].nextS = a;
+ }
+ else if (getEdge(getEdge(b).prevE).en == getEdge(b).en)
+ {
+ _aretes[getEdge(b).prevE].nextE = a;
+ }
+ }
+ if (getEdge(b).nextE >= 0 && getEdge(b).nextE != a)
+ {
+ if (getEdge(getEdge(b).nextE).st == getEdge(b).en)
+ {
+ _aretes[getEdge(b).nextE].prevS = a;
+ }
+ else if (getEdge(getEdge(b).nextE).en == getEdge(b).en)
+ {
+ _aretes[getEdge(b).nextE].prevE = a;
+ }
+ }
+
+
+ for (int i = 0; i < 2; i++) {
+ int p = getEdge(b).st;
+ if (p >= 0) {
+ if (getPoint(p).incidentEdge[i] == b) {
+ _pts[p].incidentEdge[i] = a;
+ }
+ }
+
+ p = getEdge(b).en;
+ if (p >= 0) {
+ if (getPoint(p).incidentEdge[i] == b) {
+ _pts[p].incidentEdge[i] = a;
+ }
+ }
+
+ p = getEdge(a).st;
+ if (p >= 0) {
+ if (getPoint(p).incidentEdge[i] == numberOfEdges()) {
+ _pts[p].incidentEdge[i] = b;
+ }
+ }
+
+ p = getEdge(a).en;
+ if (p >= 0) {
+ if (getPoint(p).incidentEdge[i] == numberOfEdges()) {
+ _pts[p].incidentEdge[i] = b;
+ }
+ }
+
+ }
+
+ if (getEdge(a).prevS == b)
+ _aretes[a].prevS = a;
+ if (getEdge(a).prevE == b)
+ _aretes[a].prevE = a;
+ if (getEdge(a).nextS == b)
+ _aretes[a].nextS = a;
+ if (getEdge(a).nextE == b)
+ _aretes[a].nextE = a;
+ if (getEdge(b).prevS == a)
+ _aretes[a].prevS = b;
+ if (getEdge(b).prevE == a)
+ _aretes[a].prevE = b;
+ if (getEdge(b).nextS == a)
+ _aretes[a].nextS = b;
+ if (getEdge(b).nextE == a)
+ _aretes[a].nextE = b;
+
+ dg_arete swap = getEdge(a);
+ _aretes[a] = getEdge(b);
+ _aretes[b] = swap;
+ if (_has_edges_data)
+ {
+ edge_data swae = eData[a];
+ eData[a] = eData[b];
+ eData[b] = swae;
+ }
+ if (_has_sweep_src_data)
+ {
+ sweep_src_data swae = swsData[a];
+ swsData[a] = swsData[b];
+ swsData[b] = swae;
+ }
+ if (_has_sweep_dest_data)
+ {
+ sweep_dest_data swae = swdData[a];
+ swdData[a] = swdData[b];
+ swdData[b] = swae;
+ }
+ if (_has_raster_data)
+ {
+ raster_data swae = swrData[a];
+ swrData[a] = swrData[b];
+ swrData[b] = swae;
+ }
+ if (_has_back_data)
+ {
+ back_data swae = ebData[a];
+ ebData[a] = ebData[b];
+ ebData[b] = swae;
+ }
+ if (_has_voronoi_data)
+ {
+ voronoi_edge swav = voreData[a];
+ voreData[a] = voreData[b];
+ voreData[b] = swav;
+ }
+}
+void
+Shape::SwapEdges (int a, int b, int c)
+{
+ if (a == b || b == c || a == c)
+ return;
+ SwapEdges (a, b);
+ SwapEdges (b, c);
+}
+
+void
+Shape::SortEdges ()
+{
+ if (_need_edges_sorting == false) {
+ return;
+ }
+ _need_edges_sorting = false;
+
+ // allocate the edge_list array as it's needed by SortEdgesList
+ edge_list *list = (edge_list *) g_malloc(numberOfEdges() * sizeof (edge_list));
+ // for each point
+ for (int p = 0; p < numberOfPoints(); p++)
+ {
+ int const d = getPoint(p).totalDegree();
+ // if degree is greater than 1
+ if (d > 1)
+ {
+ int cb;
+ // get the first edge
+ cb = getPoint(p).incidentEdge[FIRST];
+ int nb = 0;
+ // for all the edges connected at this point, form the list
+ while (cb >= 0)
+ {
+ int n = nb++;
+ list[n].no = cb;
+ if (getEdge(cb).st == p)
+ {
+ list[n].x = getEdge(cb).dx;
+ list[n].starting = true;
+ }
+ else
+ {
+ list[n].x = -getEdge(cb).dx;
+ list[n].starting = false;
+ }
+ cb = NextAt (p, cb);
+ }
+ // sort the list
+ SortEdgesList (list, 0, nb - 1);
+ // recreate the linked list with the sorted edges
+ _pts[p].incidentEdge[FIRST] = list[0].no;
+ _pts[p].incidentEdge[LAST] = list[nb - 1].no;
+ for (int i = 0; i < nb; i++)
+ {
+ if (list[i].starting)
+ {
+ if (i > 0)
+ {
+ _aretes[list[i].no].prevS = list[i - 1].no;
+ }
+ else
+ {
+ _aretes[list[i].no].prevS = -1;
+ }
+ if (i < nb - 1)
+ {
+ _aretes[list[i].no].nextS = list[i + 1].no;
+ }
+ else
+ {
+ _aretes[list[i].no].nextS = -1;
+ }
+ }
+ else
+ {
+ if (i > 0)
+ {
+ _aretes[list[i].no].prevE = list[i - 1].no;
+ }
+ else
+ {
+ _aretes[list[i].no].prevE = -1;
+ }
+ if (i < nb - 1)
+ {
+ _aretes[list[i].no].nextE = list[i + 1].no;
+ }
+ else
+ {
+ _aretes[list[i].no].nextE = -1;
+ }
+ }
+ }
+ }
+ }
+ g_free(list);
+}
+
+int
+Shape::CmpToVert (Geom::Point ax, Geom::Point bx,bool as,bool bs)
+{
+ // these tst variables store the sign of the x and y coordinates of each of the vectors
+ // + is +1, - is -1 and 0 is 0
+ int tstAX = 0;
+ int tstAY = 0;
+ int tstBX = 0;
+ int tstBY = 0;
+ if (ax[0] > 0)
+ tstAX = 1;
+ if (ax[0] < 0)
+ tstAX = -1;
+ if (ax[1] > 0)
+ tstAY = 1;
+ if (ax[1] < 0)
+ tstAY = -1;
+ if (bx[0] > 0)
+ tstBX = 1;
+ if (bx[0] < 0)
+ tstBX = -1;
+ if (bx[1] > 0)
+ tstBY = 1;
+ if (bx[1] < 0)
+ tstBY = -1;
+
+ // calcuate quadrant for both a and b edge vectors
+ // for quadrant numbers, see the figure in header file documentation of this
+ // function
+ int quadA = 0, quadB = 0;
+ if (tstAX < 0)
+ {
+ if (tstAY < 0)
+ {
+ quadA = 7;
+ }
+ else if (tstAY == 0)
+ {
+ quadA = 6;
+ }
+ else if (tstAY > 0)
+ {
+ quadA = 5;
+ }
+ }
+ else if (tstAX == 0)
+ {
+ if (tstAY < 0)
+ {
+ quadA = 0;
+ }
+ else if (tstAY == 0)
+ {
+ quadA = -1;
+ }
+ else if (tstAY > 0)
+ {
+ quadA = 4;
+ }
+ }
+ else if (tstAX > 0)
+ {
+ if (tstAY < 0)
+ {
+ quadA = 1;
+ }
+ else if (tstAY == 0)
+ {
+ quadA = 2;
+ }
+ else if (tstAY > 0)
+ {
+ quadA = 3;
+ }
+ }
+ if (tstBX < 0)
+ {
+ if (tstBY < 0)
+ {
+ quadB = 7;
+ }
+ else if (tstBY == 0)
+ {
+ quadB = 6;
+ }
+ else if (tstBY > 0)
+ {
+ quadB = 5;
+ }
+ }
+ else if (tstBX == 0)
+ {
+ if (tstBY < 0)
+ {
+ quadB = 0;
+ }
+ else if (tstBY == 0)
+ {
+ quadB = -1;
+ }
+ else if (tstBY > 0)
+ {
+ quadB = 4;
+ }
+ }
+ else if (tstBX > 0)
+ {
+ if (tstBY < 0)
+ {
+ quadB = 1;
+ }
+ else if (tstBY == 0)
+ {
+ quadB = 2;
+ }
+ else if (tstBY > 0)
+ {
+ quadB = 3;
+ }
+ }
+
+
+ // B should have a smaller quadrant number, if the opposite is true, we need to swap
+ if (quadA < quadB)
+ return 1;
+ if (quadA > quadB)
+ return -1;
+
+ // if the quadrants are same, we need to do more maths to figure out their relative orientation
+
+ Geom::Point av, bv;
+ av = ax;
+ bv = bx;
+
+ // take cross from av to bv
+ // cross product in SVG coordinates is different visually. With your right hand, let index finger point to vector
+ // av, and let middle finger point to vector bv, if thumb is out of page, cross is negative, if into the page, it's
+ // positive
+ double si = cross(av, bv);
+ // if the angle that bv makes from -y axis is smaller than the one av makes, our arrangement is good and needs no swap
+ // this ideal orientation will give a negative cross product (thumb out of page)
+ int tstSi = 0;
+ if (si > 0.000001) tstSi = 1; // if positive cross product, swap needed
+ if (si < -0.000001) tstSi = -1; // if negative cross product, we are good
+
+ // if the edges are kinda on top of each other
+ // I have no idea if there is a reason behind these two rules below
+ if ( tstSi == 0 ) {
+ if ( as && !bs ) return -1; // if b ends at point and a starts, we are good
+ if ( !as && bs ) return 1; // if b starts at point and a ends, swap
+ }
+
+ // if we stil can't decide whether a swap is needed or not (both edges on top of each other and both starting
+ // from the point) or ending at the point, we return 0
+ return tstSi;
+}
+
+void
+Shape::SortEdgesList (edge_list * list, int s, int e)
+{
+ if (s >= e)
+ return;
+ if (e == s + 1) {
+ int cmpval=CmpToVert (list[e].x, list[s].x,list[e].starting,list[s].starting);
+ if ( cmpval > 0 ) { // priorite aux sortants
+ edge_list swap = list[s];
+ list[s] = list[e];
+ list[e] = swap;
+ }
+ return;
+ }
+
+ int ppos = (s + e) / 2;
+ int plast = ppos;
+ Geom::Point pvalx = list[ppos].x;
+ bool pvals = list[ppos].starting;
+
+ int le = s, ri = e;
+ while (le < ppos || ri > plast)
+ {
+ if (le < ppos)
+ {
+ do
+ {
+ int test = CmpToVert (pvalx, list[le].x,pvals,list[le].starting);
+ if (test == 0)
+ {
+ // on colle les valeurs egales au pivot ensemble
+ if (le < ppos - 1)
+ {
+ edge_list swap = list[le];
+ list[le] = list[ppos - 1];
+ list[ppos - 1] = list[ppos];
+ list[ppos] = swap;
+ ppos--;
+ continue; // sans changer le
+ }
+ else if (le == ppos - 1)
+ {
+ ppos--;
+ break;
+ }
+ else
+ {
+ // oupsie
+ break;
+ }
+ }
+ if (test > 0)
+ {
+ break;
+ }
+ le++;
+ }
+ while (le < ppos);
+ }
+ if (ri > plast)
+ {
+ do
+ {
+ int test = CmpToVert (pvalx, list[ri].x,pvals,list[ri].starting);
+ if (test == 0)
+ {
+ // on colle les valeurs egales au pivot ensemble
+ if (ri > plast + 1)
+ {
+ edge_list swap = list[ri];
+ list[ri] = list[plast + 1];
+ list[plast + 1] = list[plast];
+ list[plast] = swap;
+ plast++;
+ continue; // sans changer ri
+ }
+ else if (ri == plast + 1)
+ {
+ plast++;
+ break;
+ }
+ else
+ {
+ // oupsie
+ break;
+ }
+ }
+ if (test < 0)
+ {
+ break;
+ }
+ ri--;
+ }
+ while (ri > plast);
+ }
+
+ if (le < ppos)
+ {
+ if (ri > plast)
+ {
+ edge_list swap = list[le];
+ list[le] = list[ri];
+ list[ri] = swap;
+ le++;
+ ri--;
+ }
+ else if (le < ppos - 1)
+ {
+ edge_list swap = list[ppos - 1];
+ list[ppos - 1] = list[plast];
+ list[plast] = list[le];
+ list[le] = swap;
+ ppos--;
+ plast--;
+ }
+ else if (le == ppos - 1)
+ {
+ edge_list swap = list[plast];
+ list[plast] = list[le];
+ list[le] = swap;
+ ppos--;
+ plast--;
+ }
+ else
+ {
+ break;
+ }
+ }
+ else
+ {
+ if (ri > plast + 1)
+ {
+ edge_list swap = list[plast + 1];
+ list[plast + 1] = list[ppos];
+ list[ppos] = list[ri];
+ list[ri] = swap;
+ ppos++;
+ plast++;
+ }
+ else if (ri == plast + 1)
+ {
+ edge_list swap = list[ppos];
+ list[ppos] = list[ri];
+ list[ri] = swap;
+ ppos++;
+ plast++;
+ }
+ else
+ {
+ break;
+ }
+ }
+ }
+ SortEdgesList (list, s, ppos - 1);
+ SortEdgesList (list, plast + 1, e);
+
+}
+
+
+
+/*
+ *
+ */
+void
+Shape::ConnectStart (int p, int b)
+{
+ if (getEdge(b).st >= 0)
+ DisconnectStart (b);
+
+ _aretes[b].st = p;
+ _pts[p].dO++;
+ _aretes[b].nextS = -1;
+ _aretes[b].prevS = getPoint(p).incidentEdge[LAST];
+ if (getPoint(p).incidentEdge[LAST] >= 0)
+ {
+ if (getEdge(getPoint(p).incidentEdge[LAST]).st == p)
+ {
+ _aretes[getPoint(p).incidentEdge[LAST]].nextS = b;
+ }
+ else if (getEdge(getPoint(p).incidentEdge[LAST]).en == p)
+ {
+ _aretes[getPoint(p).incidentEdge[LAST]].nextE = b;
+ }
+ }
+ _pts[p].incidentEdge[LAST] = b;
+ if (getPoint(p).incidentEdge[FIRST] < 0)
+ _pts[p].incidentEdge[FIRST] = b;
+}
+
+void
+Shape::ConnectEnd (int p, int b)
+{
+ if (getEdge(b).en >= 0)
+ DisconnectEnd (b);
+ _aretes[b].en = p;
+ _pts[p].dI++;
+ _aretes[b].nextE = -1;
+ _aretes[b].prevE = getPoint(p).incidentEdge[LAST];
+ if (getPoint(p).incidentEdge[LAST] >= 0)
+ {
+ if (getEdge(getPoint(p).incidentEdge[LAST]).st == p)
+ {
+ _aretes[getPoint(p).incidentEdge[LAST]].nextS = b;
+ }
+ else if (getEdge(getPoint(p).incidentEdge[LAST]).en == p)
+ {
+ _aretes[getPoint(p).incidentEdge[LAST]].nextE = b;
+ }
+ }
+ _pts[p].incidentEdge[LAST] = b;
+ if (getPoint(p).incidentEdge[FIRST] < 0)
+ _pts[p].incidentEdge[FIRST] = b;
+}
+
+void
+Shape::DisconnectStart (int b)
+{
+ if (getEdge(b).st < 0)
+ return;
+ _pts[getEdge(b).st].dO--;
+ if (getEdge(b).prevS >= 0)
+ {
+ if (getEdge(getEdge(b).prevS).st == getEdge(b).st)
+ {
+ _aretes[getEdge(b).prevS].nextS = getEdge(b).nextS;
+ }
+ else if (getEdge(getEdge(b).prevS).en == getEdge(b).st)
+ {
+ _aretes[getEdge(b).prevS].nextE = getEdge(b).nextS;
+ }
+ }
+ if (getEdge(b).nextS >= 0)
+ {
+ if (getEdge(getEdge(b).nextS).st == getEdge(b).st)
+ {
+ _aretes[getEdge(b).nextS].prevS = getEdge(b).prevS;
+ }
+ else if (getEdge(getEdge(b).nextS).en == getEdge(b).st)
+ {
+ _aretes[getEdge(b).nextS].prevE = getEdge(b).prevS;
+ }
+ }
+ if (getPoint(getEdge(b).st).incidentEdge[FIRST] == b)
+ _pts[getEdge(b).st].incidentEdge[FIRST] = getEdge(b).nextS;
+ if (getPoint(getEdge(b).st).incidentEdge[LAST] == b)
+ _pts[getEdge(b).st].incidentEdge[LAST] = getEdge(b).prevS;
+ _aretes[b].st = -1;
+}
+
+void
+Shape::DisconnectEnd (int b)
+{
+ if (getEdge(b).en < 0)
+ return;
+ _pts[getEdge(b).en].dI--;
+ if (getEdge(b).prevE >= 0)
+ {
+ if (getEdge(getEdge(b).prevE).st == getEdge(b).en)
+ {
+ _aretes[getEdge(b).prevE].nextS = getEdge(b).nextE;
+ }
+ else if (getEdge(getEdge(b).prevE).en == getEdge(b).en)
+ {
+ _aretes[getEdge(b).prevE].nextE = getEdge(b).nextE;
+ }
+ }
+ if (getEdge(b).nextE >= 0)
+ {
+ if (getEdge(getEdge(b).nextE).st == getEdge(b).en)
+ {
+ _aretes[getEdge(b).nextE].prevS = getEdge(b).prevE;
+ }
+ else if (getEdge(getEdge(b).nextE).en == getEdge(b).en)
+ {
+ _aretes[getEdge(b).nextE].prevE = getEdge(b).prevE;
+ }
+ }
+ if (getPoint(getEdge(b).en).incidentEdge[FIRST] == b)
+ _pts[getEdge(b).en].incidentEdge[FIRST] = getEdge(b).nextE;
+ if (getPoint(getEdge(b).en).incidentEdge[LAST] == b)
+ _pts[getEdge(b).en].incidentEdge[LAST] = getEdge(b).prevE;
+ _aretes[b].en = -1;
+}
+
+
+void
+Shape::Inverse (int b)
+{
+ int swap;
+ swap = getEdge(b).st;
+ _aretes[b].st = getEdge(b).en;
+ _aretes[b].en = swap;
+ swap = getEdge(b).prevE;
+ _aretes[b].prevE = getEdge(b).prevS;
+ _aretes[b].prevS = swap;
+ swap = getEdge(b).nextE;
+ _aretes[b].nextE = getEdge(b).nextS;
+ _aretes[b].nextS = swap;
+ _aretes[b].dx = -getEdge(b).dx;
+ if (getEdge(b).st >= 0)
+ {
+ _pts[getEdge(b).st].dO++;
+ _pts[getEdge(b).st].dI--;
+ }
+ if (getEdge(b).en >= 0)
+ {
+ _pts[getEdge(b).en].dO--;
+ _pts[getEdge(b).en].dI++;
+ }
+ if (_has_edges_data)
+ eData[b].weight = -eData[b].weight;
+ if (_has_sweep_dest_data)
+ {
+ int swap = swdData[b].leW;
+ swdData[b].leW = swdData[b].riW;
+ swdData[b].riW = swap;
+ }
+ if (_has_back_data)
+ {
+ double swat = ebData[b].tSt;
+ ebData[b].tSt = ebData[b].tEn;
+ ebData[b].tEn = swat;
+ }
+ if (_has_voronoi_data)
+ {
+ int swai = voreData[b].leF;
+ voreData[b].leF = voreData[b].riF;
+ voreData[b].riF = swai;
+ }
+}
+void
+Shape::CalcBBox (bool strict_degree)
+{
+ if (_bbox_up_to_date)
+ return;
+ if (hasPoints() == false)
+ {
+ leftX = rightX = topY = bottomY = 0;
+ _bbox_up_to_date = true;
+ return;
+ }
+ leftX = rightX = getPoint(0).x[0];
+ topY = bottomY = getPoint(0).x[1];
+ bool not_set=true;
+ for (int i = 0; i < numberOfPoints(); i++)
+ {
+ if ( strict_degree == false || getPoint(i).dI > 0 || getPoint(i).dO > 0 ) {
+ if ( not_set ) {
+ leftX = rightX = getPoint(i).x[0];
+ topY = bottomY = getPoint(i).x[1];
+ not_set=false;
+ } else {
+ if ( getPoint(i).x[0] < leftX) leftX = getPoint(i).x[0];
+ if ( getPoint(i).x[0] > rightX) rightX = getPoint(i).x[0];
+ if ( getPoint(i).x[1] < topY) topY = getPoint(i).x[1];
+ if ( getPoint(i).x[1] > bottomY) bottomY = getPoint(i).x[1];
+ }
+ }
+ }
+
+ _bbox_up_to_date = true;
+}
+
+// winding of a point with respect to the Shape
+// 0= outside
+// 1= inside (or -1, that usually the same)
+// other=depends on your fill rule
+// if the polygon is uncrossed, it's all the same, usually
+int
+Shape::PtWinding (const Geom::Point px) const
+{
+ int lr = 0, ll = 0, rr = 0;
+
+ for (int i = 0; i < numberOfEdges(); i++)
+ {
+ Geom::Point const adir = getEdge(i).dx;
+
+ Geom::Point const ast = getPoint(getEdge(i).st).x;
+ Geom::Point const aen = getPoint(getEdge(i).en).x;
+
+ //int const nWeight = eData[i].weight;
+ int const nWeight = 1;
+
+ if (ast[0] < aen[0]) {
+ if (ast[0] > px[0]) continue;
+ if (aen[0] < px[0]) continue;
+ } else {
+ if (ast[0] < px[0]) continue;
+ if (aen[0] > px[0]) continue;
+ }
+ if (ast[0] == px[0]) {
+ if (ast[1] >= px[1]) continue;
+ if (aen[0] == px[0]) continue;
+ if (aen[0] < px[0]) ll += nWeight; else rr -= nWeight;
+ continue;
+ }
+ if (aen[0] == px[0]) {
+ if (aen[1] >= px[1]) continue;
+ if (ast[0] == px[0]) continue;
+ if (ast[0] < px[0]) ll -= nWeight; else rr += nWeight;
+ continue;
+ }
+
+ if (ast[1] < aen[1]) {
+ if (ast[1] >= px[1]) continue;
+ } else {
+ if (aen[1] >= px[1]) continue;
+ }
+
+ Geom::Point const diff = px - ast;
+ double const cote = cross(adir, diff);
+ if (cote == 0) continue;
+ if (cote < 0) {
+ if (ast[0] > px[0]) lr += nWeight;
+ } else {
+ if (ast[0] < px[0]) lr -= nWeight;
+ }
+ }
+ return lr + (ll + rr) / 2;
+}
+
+
+void Shape::initialisePointData()
+{
+ if (_point_data_initialised)
+ return;
+ int const N = numberOfPoints();
+
+ for (int i = 0; i < N; i++) {
+ pData[i].pending = 0;
+ pData[i].edgeOnLeft = -1;
+ pData[i].nextLinkedPoint = -1;
+ pData[i].rx[0] = Round(getPoint(i).x[0]);
+ pData[i].rx[1] = Round(getPoint(i).x[1]);
+ }
+
+ _point_data_initialised = true;
+}
+
+void Shape::initialiseEdgeData()
+{
+ int const N = numberOfEdges();
+
+ for (int i = 0; i < N; i++) {
+ eData[i].rdx = pData[getEdge(i).en].rx - pData[getEdge(i).st].rx;
+ eData[i].length = dot(eData[i].rdx, eData[i].rdx);
+ eData[i].ilength = 1 / eData[i].length;
+ eData[i].sqlength = sqrt(eData[i].length);
+ eData[i].isqlength = 1 / eData[i].sqlength;
+ eData[i].siEd = eData[i].rdx[1] * eData[i].isqlength;
+ eData[i].coEd = eData[i].rdx[0] * eData[i].isqlength;
+
+ if (eData[i].siEd < 0) {
+ eData[i].siEd = -eData[i].siEd;
+ eData[i].coEd = -eData[i].coEd;
+ }
+
+ swsData[i].misc = nullptr;
+ swsData[i].firstLinkedPoint = -1;
+ swsData[i].stPt = swsData[i].enPt = -1;
+ swsData[i].leftRnd = swsData[i].rightRnd = -1;
+ swsData[i].nextSh = nullptr;
+ swsData[i].nextBo = -1;
+ swsData[i].curPoint = -1;
+ swsData[i].doneTo = -1;
+ }
+}
+
+
+void Shape::clearIncidenceData()
+{
+ g_free(iData);
+ iData = nullptr;
+ nbInc = maxInc = 0;
+}
+
+
+
+/**
+ * A directed graph is Eulerian iff every vertex has equal indegree and outdegree.
+ * http://mathworld.wolfram.com/EulerianGraph.html
+ *
+ * \param s Directed shape.
+ * \return true if s is Eulerian.
+ */
+
+bool directedEulerian(Shape const *s)
+{
+ for (int i = 0; i < s->numberOfPoints(); i++) {
+ if (s->getPoint(i).dI != s->getPoint(i).dO) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+
+
+/**
+ * \param s Shape.
+ * \param p Point.
+ * \return Minimum distance from p to any of the points or edges of s.
+ */
+
+double distance(Shape const *s, Geom::Point const &p)
+{
+ if ( s->hasPoints() == false) {
+ return 0.0;
+ }
+
+ /* Find the minimum distance from p to one of the points on s.
+ ** Computing the dot product of the difference vector gives
+ ** us the distance squared; we can leave the square root
+ ** until the end.
+ */
+ double bdot = Geom::dot(p - s->getPoint(0).x, p - s->getPoint(0).x);
+
+ for (int i = 0; i < s->numberOfPoints(); i++) {
+ Geom::Point const offset( p - s->getPoint(i).x );
+ double ndot = Geom::dot(offset, offset);
+ if ( ndot < bdot ) {
+ bdot = ndot;
+ }
+ }
+
+ for (int i = 0; i < s->numberOfEdges(); i++) {
+ if ( s->getEdge(i).st >= 0 && s->getEdge(i).en >= 0 ) {
+ /* The edge has start and end points */
+ Geom::Point const st(s->getPoint(s->getEdge(i).st).x); // edge start
+ Geom::Point const en(s->getPoint(s->getEdge(i).en).x); // edge end
+
+ Geom::Point const d(p - st); // vector between p and edge start
+ Geom::Point const e(en - st); // vector of the edge
+ double const el = Geom::dot(e, e); // edge length
+
+ /* Update bdot if appropriate */
+ if ( el > 0.001 ) {
+ double const npr = Geom::dot(d, e);
+ if ( npr > 0 && npr < el ) {
+ double const nl = fabs( Geom::cross(d, e) );
+ double ndot = nl * nl / el;
+ if ( ndot < bdot ) {
+ bdot = ndot;
+ }
+ }
+ }
+ }
+ }
+
+ return sqrt(bdot);
+}
+
+
+
+/**
+ * Returns true iff the L2 distance from \a thePt to this shape is <= \a max_l2.
+ * Distance = the min of distance to its points and distance to its edges.
+ * Points without edges are considered, which is maybe unwanted...
+ *
+ * This is largely similar to distance().
+ *
+ * \param s Shape.
+ * \param p Point.
+ * \param max_l2 L2 distance.
+ */
+
+bool distanceLessThanOrEqual(Shape const *s, Geom::Point const &p, double const max_l2)
+{
+ if ( s->hasPoints() == false ) {
+ return false;
+ }
+
+ /* TODO: Consider using bbox to return early, perhaps conditional on nbPt or nbAr. */
+
+ /* TODO: Efficiency: In one test case (scribbling with the freehand tool to create a small number of long
+ ** path elements), changing from a Distance method to a DistanceLE method reduced this
+ ** function's CPU time from about 21% of total inkscape CPU time to 14-15% of total inkscape
+ ** CPU time, due to allowing early termination. I don't know how much the L1 test helps, it
+ ** may well be a case of premature optimization. Consider testing dot(offset, offset)
+ ** instead.
+ */
+
+ double const max_l1 = max_l2 * M_SQRT2;
+ for (int i = 0; i < s->numberOfPoints(); i++) {
+ Geom::Point const offset( p - s->getPoint(i).x );
+ double const l1 = Geom::L1(offset);
+ if ( (l1 <= max_l2) || ((l1 <= max_l1) && (Geom::L2(offset) <= max_l2)) ) {
+ return true;
+ }
+ }
+
+ for (int i = 0; i < s->numberOfEdges(); i++) {
+ if ( s->getEdge(i).st >= 0 && s->getEdge(i).en >= 0 ) {
+ Geom::Point const st(s->getPoint(s->getEdge(i).st).x);
+ Geom::Point const en(s->getPoint(s->getEdge(i).en).x);
+ Geom::Point const d(p - st);
+ Geom::Point const e(en - st);
+ double const el = Geom::L2(e);
+ if ( el > 0.001 ) {
+ Geom::Point const e_unit(e / el);
+ double const npr = Geom::dot(d, e_unit);
+ if ( npr > 0 && npr < el ) {
+ double const nl = fabs(Geom::cross(d, e_unit));
+ if ( nl <= max_l2 ) {
+ return true;
+ }
+ }
+ }
+ }
+ }
+
+ return false;
+}
+
+//};
+
diff --git a/src/livarot/Shape.h b/src/livarot/Shape.h
new file mode 100644
index 0000000..bc2a547
--- /dev/null
+++ b/src/livarot/Shape.h
@@ -0,0 +1,1202 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * TODO: insert short description here
+ *//*
+ * Authors: see git history
+ *
+ * Copyright (C) 2018 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef my_shape
+#define my_shape
+
+#include <cmath>
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+#include <vector>
+#include <2geom/point.h>
+
+#include "livarot/LivarotDefs.h"
+#include "object/object-set.h" // For BooleanOp
+
+class Path;
+class FloatLigne;
+
+class SweepTree;
+class SweepTreeList;
+class SweepEventQueue;
+
+enum {
+ tweak_mode_grow,
+ tweak_mode_push,
+ tweak_mode_repel,
+ tweak_mode_roughen
+};
+
+/*
+ * the Shape class (was the Digraph class, as the header says) stores digraphs (no kidding!) of which
+ * a very interesting kind are polygons.
+ * the main use of this class is the ConvertToShape() (or Booleen(), quite the same) function, which
+ * removes all problems a polygon can present: duplicate points or edges, self-intersection. you end up with a
+ * full-fledged polygon
+ */
+
+// possible values for the "type" field in the Shape class:
+enum
+{
+ shape_graph = 0, // it's just a graph; a bunch of edges, maybe intersections
+ shape_polygon = 1, // a polygon: intersection-free, edges oriented so that the inside is on their left
+ shape_polypatch = 2 // a graph without intersection; each face is a polygon (not yet used)
+};
+
+class BitLigne;
+class AlphaLigne;
+
+/**
+ * A class to store/manipulate directed graphs.
+ *
+ * This class is at the heart of everything we do in Livarot. When you first populate a Shape by calling
+ * Path::Fill, it makes a directed graph of the type shape_graph. This one is exactly identical to the original
+ * polyline except that it's a graph. Later, you call Shape::ConvertToShape to create another directed graph
+ * from this one that is totally intersection free. All the intersections would have been calculated, edges broken
+ * up at those points, all edges flipped such that inside is to their left. You ofcourse need a fill rule to do this.
+ *
+ * Once that's done, you can do all interesting operations such as Tweaking, Offsetting and Boolean Operations.
+ */
+class Shape
+{
+public:
+
+ /**
+ * A structure to store back data for an edge.
+ */
+ struct back_data
+ {
+ int pathID; /*!< This is a unique number unique to a Path object given by the user to the Path::Fill function. */
+ int pieceID; /*!< The path command to which this edge belongs to in the original Path object. */
+ double tSt; /*!< Time value in that path command for this edge's start point. */
+ double tEn; /*!< Time value in that path command for this edge's end point. */
+ };
+
+ struct voronoi_point
+ { // info for points treated as points of a voronoi diagram (obtained by MakeShape())
+ double value; // distance to source
+ int winding; // winding relatively to source
+ };
+
+ struct voronoi_edge
+ { // info for edges, treated as approximation of edges of the voronoi diagram
+ int leF, riF; // left and right site
+ double leStX, leStY, riStX, riStY; // on the left side: (leStX,leStY) is the smallest vector from the source to st
+ // etc...
+ double leEnX, leEnY, riEnX, riEnY;
+ };
+
+ struct quick_raster_data
+ {
+ double x; // x-position on the sweepline
+ int bord; // index of the edge
+ int ind; // index of qrsData elem for edge (ie inverse of the bord)
+ int next,prev; // dbl linkage
+ };
+
+ /**
+ * Enum describing all the events that can happen to a sweepline.
+ */
+ enum sTreeChangeType
+ {
+ EDGE_INSERTED = 0, /*!< A new edge got added. */
+ EDGE_REMOVED = 1, /*!< An edge got removed. */
+ INTERSECTION = 2 /*!< An intersection got detected. */
+ };
+
+ /**
+ * A structure that represents a change that took place in the sweepline.
+ */
+ struct sTreeChange
+ {
+ sTreeChangeType type; // type of modification to the sweepline:
+ int ptNo; // point at which the modification takes place
+
+ Shape *src; // left edge (or unique edge if not an intersection) involved in the event
+ int bord;
+ Shape *osrc; // right edge (if intersection)
+ int obord;
+ Shape *lSrc; // edge directly on the left in the sweepline at the moment of the event
+ int lBrd;
+ Shape *rSrc; // edge directly on the right
+ int rBrd;
+ };
+
+ struct incidenceData
+ {
+ int nextInc; // next incidence in the linked list
+ int pt; // point incident to the edge (there is one list per edge)
+ double theta; // coordinate of the incidence on the edge
+ };
+
+ Shape();
+ virtual ~Shape();
+
+ void MakeBackData(bool nVal);
+ void MakeVoronoiData(bool nVal);
+
+ void Affiche();
+
+ // insertion/deletion/movement of elements in the graph
+ void Copy(Shape *a);
+ // -reset the graph, and ensure there's room for n points and m edges
+
+ /**
+ * Clear all data.
+ *
+ * Set shape type to shape_polygon. Clear all points and edges. Make room for
+ * enough points and edges.
+ *
+ * @param n Number of points to make space for.
+ * @param m Number of edges to make space for.
+ */
+ void Reset(int n = 0, int m = 0);
+ // -points:
+ int AddPoint(const Geom::Point x); // as the function name says
+ // returns the index at which the point has been added in the array
+ void SubPoint(int p); // removes the point at index p
+ // nota: this function relocates the last point to the index p
+ // so don't trust point indices if you use SubPoint
+ void SwapPoints(int a, int b); // swaps 2 points at indices a and b
+ void SwapPoints(int a, int b, int c); // swaps 3 points: c <- a <- b <- c
+
+
+ /**
+ * Sort the points (all points) only if needed. (checked by flag)
+ *
+ * See the index version of SortPoints since this is exactly the same.
+ */
+ void SortPoints(); // sorts the points if needed (checks the need_points_sorting flag)
+
+ // -edges:
+ // add an edge between points of indices st and en
+ int AddEdge(int st, int en);
+ // return the edge index in the array
+
+ // add an edge between points of indices st and en
+ int AddEdge(int st, int en, int leF, int riF);
+ // return the edge index in the array
+
+ // version for the voronoi (with faces IDs)
+ void SubEdge(int e); // removes the edge at index e (same remarks as for SubPoint)
+ void SwapEdges(int a, int b); // swaps 2 edges
+ void SwapEdges(int a, int b, int c); // swaps 3 edges
+
+ /**
+ * Sort all edges (clockwise) around each point.
+ *
+ * The function operates on each point and ensures that the linked list of the edges
+ * connected to a point is in the clockwise direction spatially. The clockwise
+ * angle that an edge line segment makes with the -y axis should increase (or remain same) as we move
+ * forward in the linked list of edges.
+ *
+ * This sorting is done using edge vectors however note that if an edge ends at a point instead of starting
+ * from there, we invert the edge to make the edge vector look like it started from there.
+ */
+ void SortEdges(); // sort the edges if needed (checks the need_edges_sorting falg)
+
+ // primitives for topological manipulations
+
+ // endpoint of edge at index b that is different from the point p
+ inline int Other(int p, int b) const
+ {
+ if (getEdge(b).st == p) {
+ return getEdge(b).en;
+ }
+ return getEdge(b).st;
+ }
+
+ // next edge (after edge b) in the double-linked list at point p
+ inline int NextAt(int p, int b) const
+ {
+ if (p == getEdge(b).st) {
+ return getEdge(b).nextS;
+ }
+ else if (p == getEdge(b).en) {
+ return getEdge(b).nextE;
+ }
+
+ return -1;
+ }
+
+ // previous edge
+ inline int PrevAt(int p, int b) const
+ {
+ if (p == getEdge(b).st) {
+ return getEdge(b).prevS;
+ }
+ else if (p == getEdge(b).en) {
+ return getEdge(b).prevE;
+ }
+
+ return -1;
+ }
+
+ // same as NextAt, but the list is considered circular
+ inline int CycleNextAt(int p, int b) const
+ {
+ if (p == getEdge(b).st) {
+ if (getEdge(b).nextS < 0) {
+ return getPoint(p).incidentEdge[FIRST];
+ }
+ return getEdge(b).nextS;
+ } else if (p == getEdge(b).en) {
+ if (getEdge(b).nextE < 0) {
+ return getPoint(p).incidentEdge[FIRST];
+ }
+
+ return getEdge(b).nextE;
+ }
+
+ return -1;
+ }
+
+ // same as PrevAt, but the list is considered circular
+ inline int CyclePrevAt(int p, int b) const
+ {
+ if (p == getEdge(b).st) {
+ if (getEdge(b).prevS < 0) {
+ return getPoint(p).incidentEdge[LAST];
+ }
+ return getEdge(b).prevS;
+ } else if (p == getEdge(b).en) {
+ if (getEdge(b).prevE < 0) {
+ return getPoint(p).incidentEdge[LAST];
+ }
+ return getEdge(b).prevE;
+ }
+
+ return -1;
+ }
+
+ void ConnectStart(int p, int b); // set the point p as the start of edge b
+ void ConnectEnd(int p, int b); // set the point p as the end of edge b
+ void DisconnectStart(int b); // disconnect edge b from its start point
+ void DisconnectEnd(int b); // disconnect edge b from its end point
+
+ // reverses edge b (start <-> end)
+ void Inverse(int b);
+ // calc bounding box and sets leftX,rightX,topY and bottomY to their values
+ void CalcBBox(bool strict_degree = false);
+
+ // debug function: plots the graph (mac only)
+ void Plot(double ix, double iy, double ir, double mx, double my, bool doPoint,
+ bool edgesNo, bool pointNo, bool doDir, char *fileName);
+
+ // transforms a polygon in a "forme" structure, ie a set of contours, which can be holes (see ShapeUtils.h)
+ // return NULL in case it's not possible
+
+ /**
+ * Extract contours from a directed graph.
+ *
+ * The function doesn't care about any back data and thus all contours will be made up
+ * of line segments. Any original curves would be lost.
+ *
+ * The traversal algorithm is totally identical to GetWindings with minor differences.
+ *
+ * @param[out] dest Pointer to the path where the extracted contours will be stored.
+ */
+ void ConvertToForme(Path *dest);
+
+ // version to use when conversion was done with ConvertWithBackData(): will attempt to merge segment belonging to
+ // the same curve
+ // nota: apparently the function doesn't like very small segments of arc
+
+ /**
+ * Extract contours from a directed graph while using back data.
+ *
+ * Since back data is used, the original curves are preserved.
+ *
+ * @param[out] dest Pointer to the path where the extracted contours will be stored.
+ * @param nbP Number of paths that were originally fed to the directed graph with Path::Fill.
+ * @param orig An array of pointers to Path, one Path object for each path id in the graph.
+ * @param splitWhenForced TODO: Figure this out.
+ */
+ void ConvertToForme(Path *dest, int nbP, Path **orig, bool splitWhenForced = false);
+ // version trying to recover the nesting of subpaths (ie: holes)
+ void ConvertToFormeNested(Path *dest, int nbP, Path **orig, int wildPath, int &nbNest,
+ int *&nesting, int *&contStart, bool splitWhenForced = false);
+
+ // sweeping a digraph to produce a intersection-free polygon
+ // return 0 if everything is ok and a return code otherwise (see LivarotDefs.h)
+ // the input is the Shape "a"
+ // directed=true <=> non-zero fill rule
+
+ /**
+ * Using a given fill rule, find all intersections in the shape given, create a new
+ * intersection free shape in the instance.
+ *
+ * The word "instance" or "this" is used throughout the documentation to refer to the instance on which
+ * the function was called.
+ *
+ * Details of the algorithm:
+ * The function does four things more or less:
+ * 1. Find all self-intersections in the shape.
+ * 2. Reconstruct the directed graph with the intersections now converted to vertices.
+ * 3. Compute winding number seeds for later use by GetWindings.
+ * 4. Do some processing on edges by calling AssembleAretes.
+ * 5. Compute winding numbers and accordingly manipulate edges. (Deciding whether to keep, invert or destroy them)
+ *
+ * Finding self-intersections and reconstruction happens simultaneously. The function has a huge loop that moves
+ * a sweepline top to bottom, finding intersections and reconstructing the new directed graph. Edges are added/removed
+ * in the sweepline tree sTree and intersections detected go in sEvts. All events (Edge Addition/Removal/Intersection)
+ * that take place at a constant `y` value are recorded in an array named `chgts` and the function call to CheckEdges
+ * does the reconstruction.
+ *
+ * One important thing to note is that usually in a Bentley-Ottman sweepline algorithm, the heap also contains endpoints
+ * (where edges start or end) but in this implementation that's not the case. The main loop takes care of the endpoints
+ * and the heap takes care of intersections only.
+ *
+ * If you want a good theoretical overview of how all these things are done, please see the docs in livarot-doxygen.cpp.
+ *
+ * @param a The pointer to the shape that we want to process.
+ * @param directed The fill rule.
+ * @param invert TODO: Be sure about what this does
+ *
+ * @return 0 if everything went good, error code otherwise. (see LivarotDefs.h)
+ */
+ int ConvertToShape(Shape *a, FillRule directed = fill_nonZero, bool invert = false);
+
+
+ // directed=false <=> even-odd fill rule
+ // invert=true: make as if you inverted all edges in the source
+ int Reoriente(Shape *a); // subcase of ConvertToShape: the input a is already intersection-free
+ // all that's missing are the correct directions of the edges
+ // Reoriented is equivalent to ConvertToShape(a,false,false) , but faster sicne
+ // it doesn't computes interections nor adjacencies
+ void ForceToPolygon(); // force the Shape to believe it's a polygon (eulerian+intersection-free+no
+ // duplicate edges+no duplicate points)
+ // be careful when using this function
+
+ // the coordinate rounding function
+ inline static double Round(double x)
+ {
+ return ldexp(rint(ldexp(x, 9)), -9);
+ }
+
+ // 2 miscannellous variations on it, to scale to and back the rounding grid
+ inline static double HalfRound(double x)
+ {
+ return ldexp(x, -9);
+ }
+
+ inline static double IHalfRound(double x)
+ {
+ return ldexp(x, 9);
+ }
+
+ // boolean operations on polygons (requests intersection-free poylygons)
+ // boolean operation types are defined in LivarotDefs.h
+ // same return code as ConvertToShape
+ int Booleen(Shape *a, Shape *b, BooleanOp mod, int cutPathID = -1);
+
+ // create a graph that is an offseted version of the graph "of"
+ // the offset is dec, with joins between edges of type "join" (see LivarotDefs.h)
+ // the result is NOT a polygon; you need a subsequent call to ConvertToShape to get a real polygon
+ int MakeOffset(Shape *of, double dec, JoinType join, double miter, bool do_profile=false, double cx = 0, double cy = 0, double radius = 0, Geom::Affine *i2doc = nullptr);
+
+ int MakeTweak (int mode, Shape *a, double dec, JoinType join, double miter, bool do_profile, Geom::Point c, Geom::Point vector, double radius, Geom::Affine *i2doc);
+
+ int PtWinding(const Geom::Point px) const; // plus rapide
+ /**
+ * Compute the winding number of the point given. (brutually)
+ *
+ * The function works by bringing in a ray from (px.x, -infinity)
+ * to (px.x, px.y) and seeing how many edges it cuts and the direction
+ * of those edges. It uses this information to compute the winding number.
+ *
+ * The way this function works is that it iterates through all the edges
+ * and for each edge it checks if the ray will intersect the edge and in
+ * which orientation. See the function body to see exactly how this works.
+ *
+ * @image html livarot-images/winding-brutal-bounds.svg
+ * @image html livarot-images/winding-brutal-endpoints-start.svg
+ * @image html livarot-images/winding-brutal-endpoints-end.svg
+ *
+ * The algorithm is quite simple. For edges that simply cut the ray, we check
+ * the direction of the edge and accordingly add/subtract from a variable to keep
+ * track of the winding. However, a different case comes up when an edge has an
+ * endpoint that cuts the ray. You can just see the direction and maybe change the same
+ * variable, but then the problem is, another edge connected to the same point will also
+ * do the same and you'd have two additions when you should only have one. Hence, the solution
+ * is, we create two variables ll and rr and add/subtract to them, then, we sum them and divide
+ * by 2 to get the contribution to the winding number.
+ *
+ * @image html livarot-images/winding-brutal-endpoints.svg
+ *
+ * @param px The point whose winding number to compute
+ *
+ * @return The winding number of the point px.
+ */
+ int Winding(const Geom::Point px) const;
+
+ // rasterization
+ void BeginRaster(float &pos, int &curPt);
+ void EndRaster();
+ void BeginQuickRaster(float &pos, int &curPt);
+ void EndQuickRaster();
+
+ void Scan(float &pos, int &curP, float to, float step);
+ void QuickScan(float &pos, int &curP, float to, bool doSort, float step);
+ void DirectScan(float &pos, int &curP, float to, float step);
+ void DirectQuickScan(float &pos, int &curP, float to, bool doSort, float step);
+
+ void Scan(float &pos, int &curP, float to, FloatLigne *line, bool exact, float step);
+ void Scan(float &pos, int &curP, float to, FillRule directed, BitLigne *line, bool exact, float step);
+ void Scan(float &pos, int &curP, float to, AlphaLigne *line, bool exact, float step);
+
+ void QuickScan(float &pos, int &curP, float to, FloatLigne* line, float step);
+ void QuickScan(float &pos, int &curP, float to, FillRule directed, BitLigne* line, float step);
+ void QuickScan(float &pos, int &curP, float to, AlphaLigne* line, float step);
+
+ void Transform(Geom::Affine const &tr)
+ {for(auto & _pt : _pts) _pt.x*=tr;}
+
+ std::vector<back_data> ebData; /*!< Stores the back data for each edge. */
+ std::vector<voronoi_point> vorpData;
+ std::vector<voronoi_edge> voreData;
+
+ int nbQRas;
+ int firstQRas;
+ int lastQRas;
+ quick_raster_data *qrsData;
+
+ std::vector<sTreeChange> chgts; /*!< An array to store all the changes that happen to a sweepline within a y value */
+ int nbInc;
+ int maxInc;
+
+ incidenceData *iData;
+ // these ones are allocated at the beginning of each sweep and freed at the end of the sweep
+ SweepTreeList *sTree; /*!< Pointer to store the sweepline tree. To our use at least, it's a linear list of the edges that intersect with sweepline. */
+ SweepEventQueue *sEvts; /*!< Intersection events that we have detected that are to come. Sorted so closest one gets popped. */
+
+ // bounding box stuff
+ double leftX, topY, rightX, bottomY;
+
+ /**
+ * A point or vertex in the directed graph.
+ *
+ * Each point keeps track of the first edge that got connected to it and the last edge
+ * that got connected to it. By connecting we mean both an edge starting at the point or
+ * an edge ending at the point. This is needed for maintaining a linked list at each point.
+ *
+ * At each point, we maintain a linked list of edges that connect to that edge. incidentEdge
+ * keeps the first and last edge of this double-linked list. The rest of the edge pointers
+ * are stored in dg_arete.
+ */
+ struct dg_point
+ {
+ Geom::Point x; /*!< The coordinates of the point. */
+ int dI; /*!< Number of edges ending on this point. */
+ int dO; /*!< Number of edges starting from this point. */
+ int incidentEdge[2]; /*!< First (index 0) and last edge (index 1) that are attached to this point. */
+ int oldDegree; /*!< TODO: Not exactly sure why this is needed. Probably somewhere the degree changes and we retain the old degree for some reason. */
+
+ int totalDegree() const { return dI + dO; }
+ };
+
+ /**
+ * An edge in the directed graph.
+ */
+ struct dg_arete
+ {
+ Geom::Point dx; /*!< edge vector (vector from start point to end point). */
+ int st; /*!< start point of the edge. */
+ int en; /*!< end point of the edge. */
+ int nextS; /*!< next edge in the double-linked list at the start point */
+ int prevS; /*!< previous edge in the double-linked list at the start point. */
+ int nextE; /*!< next edge in the double-linked list at the end point. */
+ int prevE; /*!< previous edge in the double-linked list at the end point. */
+ };
+
+ // lists of the nodes and edges
+ int maxPt; // [FIXME: remove this]
+ int maxAr; // [FIXME: remove this]
+
+ // flags
+ int type;
+
+ /**
+ * Returns number of points.
+ *
+ * @return Number of points.
+ */
+ inline int numberOfPoints() const { return _pts.size(); }
+
+ /**
+ * Do we have points?
+ *
+ * @return True if we do, false otherwise.
+ */
+ inline bool hasPoints() const { return (_pts.empty() == false); }
+
+ /**
+ * Returns number of edges.
+ *
+ * @return Number of edges.
+ */
+ inline int numberOfEdges() const { return _aretes.size(); }
+
+ /**
+ * Do we have edges?
+ *
+ * @return True if we do, false otherwise.
+ */
+ inline bool hasEdges() const { return (_aretes.empty() == false); }
+
+ /**
+ * Do the points need sorting?
+ *
+ * @return True if we do, false otherwise.
+ */
+ inline void needPointsSorting() { _need_points_sorting = true; }
+
+ /**
+ * Do the edges need sorting?
+ *
+ * @return True if we do, false otherwise.
+ */
+ inline void needEdgesSorting() { _need_edges_sorting = true; }
+
+ /**
+ * Do we have back data?
+ *
+ * @return True if we do, false otherwise.
+ */
+ inline bool hasBackData() const { return _has_back_data; }
+
+ /**
+ * Get a point.
+ *
+ * Be careful about the index.
+ *
+ * @param n Index of the point.
+ *
+ * @return Reference to the point.
+ */
+ inline dg_point const &getPoint(int n) const { return _pts[n]; }
+
+ /**
+ * Get an edge.
+ *
+ * Be careful about the index.
+ *
+ * @param n Index of the edge.
+ *
+ * @return Reference to the edge.
+ */
+ inline dg_arete const &getEdge(int n) const { return _aretes[n]; }
+
+private:
+
+ friend class SweepTree;
+ friend class SweepEvent;
+ friend class SweepEventQueue;
+
+ /**
+ * Extra data that some algorithms use.
+ */
+ struct edge_data
+ {
+ int weight; /*!< Weight of the edge. If weight is 2, it means there are two identical edges on top of each other. */
+ Geom::Point rdx; /*!< Rounded edge vector */
+ double length; /*!< length of edge vector squared. */ // <-- epic naming here folks
+ double sqlength; /*!< length of edge vector */ // <-- epic naming here too
+ double ilength; /*!< Inverse of length squared */
+ double isqlength; /*!< Inverse of length */
+ double siEd, coEd; /*!< siEd=abs(rdy/length) and coEd=rdx/length */
+ edge_data() : weight(0), length(0.0), sqlength(0.0), ilength(0.0), isqlength(0.0), siEd(0.0), coEd(0.0) {}
+ // used to determine the "most horizontal" edge between 2 edges
+ };
+
+ struct sweep_src_data
+ {
+ void *misc; // pointer to the SweepTree* in the sweepline
+ int firstLinkedPoint; // not used
+ int stPt, enPt; // start- end end- points for this edge in the resulting polygon
+ int ind; // for the GetAdjacencies function: index in the sliceSegs array (for quick deletions)
+ int leftRnd, rightRnd; // leftmost and rightmost points (in the result polygon) that are incident to
+ // the edge, for the current sweep position
+ // not set if the edge doesn't start/end or intersect at the current sweep position
+ Shape *nextSh; // nextSh and nextBo identify the next edge in the list
+ int nextBo; // they are used to maintain a linked list of edge that start/end or intersect at
+ // the current sweep position
+ int curPoint, doneTo;
+ double curT;
+ };
+
+ struct sweep_dest_data
+ {
+ void *misc; // used to check if an edge has already been seen during the depth-first search
+ int suivParc, precParc; // previous and current next edge in the depth-first search
+ int leW, riW; // left and right winding numbers for this edge
+ int ind; // order of the edges during the depth-first search
+ };
+
+ struct raster_data
+ {
+ SweepTree *misc; // pointer to the associated SweepTree* in the sweepline
+ double lastX, lastY, curX, curY; // curX;curY is the current intersection of the edge with the sweepline
+ // lastX;lastY is the intersection with the previous sweepline
+ bool sens; // true if the edge goes down, false otherwise
+ double calcX; // horizontal position of the intersection of the edge with the
+ // previous sweepline
+ double dxdy, dydx; // horizontal change per unit vertical move of the intersection with the sweepline
+ int guess;
+ };
+
+ /**
+ * Extra data for points used at various ocassions.
+ */
+ struct point_data
+ {
+ int oldInd, newInd; // back and forth indices used when sorting the points, to know where they have
+ // been relocated in the array
+ int pending; // number of intersection attached to this edge, and also used when sorting arrays
+ int edgeOnLeft; // not used (should help speeding up winding calculations)
+ int nextLinkedPoint; // not used
+ Shape *askForWindingS;
+ int askForWindingB;
+ Geom::Point rx; /*!< rounded coordinates of the point */
+ };
+
+
+ /**
+ * A structure to help with sorting edges around a point.
+ */
+ struct edge_list
+ {
+ int no;
+ bool starting;
+ Geom::Point x;
+ };
+
+ void initialisePointData();
+ void initialiseEdgeData();
+ void clearIncidenceData();
+
+ void _countUpDown(int P, int *numberUp, int *numberDown, int *upEdge, int *downEdge) const;
+ void _countUpDownTotalDegree2(int P, int *numberUp, int *numberDown, int *upEdge, int *downEdge) const;
+ void _updateIntersection(int e, int p);
+
+ // activation/deactivation of the temporary data arrays
+
+ /**
+ * Initialize the point data cache.
+ *
+ * @param nVal If set to true, it sets some flags and then resizes pData to maxPt. Does nothing if false.
+ */
+ void MakePointData(bool nVal);
+
+ /**
+ * Initialize the edge data cache.
+ *
+ * @param nVal If set to true, it sets some flags and then resizes eData to maxAr. If set to false, it clears all edge data.
+ */
+ void MakeEdgeData(bool nVal);
+
+ /**
+ * Initialize the sweep source data cache.
+ *
+ * @param nVal If set to true, it sets some flags and then resizes swsData to maxAr. If set to false, it clears all swsData.
+ */
+ void MakeSweepSrcData(bool nVal);
+
+ /**
+ * Initialize the sweep destination data cache.
+ *
+ * @param nVal If set to true, it sets some flags and then resizes swdData to maxAr. If set to false, it clears all swdData.
+ */
+ void MakeSweepDestData(bool nVal);
+
+ void MakeRasterData(bool nVal);
+ void MakeQuickRasterData(bool nVal);
+
+ /**
+ * Sort the points
+ *
+ * Nothing fancy here. Please note, sorting really means just making sure the
+ * points exist in the array in a sorted manner. All we do here is just change
+ * the point's position in the array according to their location in physical space
+ * and make sure all edges still point to the original point they were pointing to. (:-D)
+ *
+ * Sorting condition: Given two points LEFT and RIGHT (where LEFT's index < RIGHT's index)
+ * we swap only if LEFT.y > RIGHT.y || (LEFT.y == RIGHT.y && LEFT.x > RIGHT.x)
+ *
+ * After sorting, not only are the points sorted in _pts, but also in pData. So both arrays
+ * will have same index for the same point.
+ *
+ * Sorting algorithm looks like quick sort.
+ *
+ * @param s The start index.
+ * @param e The end index.
+ */
+ void SortPoints(int s, int e);
+
+ /**
+ * Sort the points (take oldInd into account)
+ *
+ * Same as SortPoints except the sorting condition.
+ *
+ * Sorting condition: Given two points LEFT and RIGHT (where LEFT's index < RIGHT's index)
+ * we swap only if LEFT.y > RIGHT.y || (LEFT.y == RIGHT.y && LEFT.x > RIGHT.x) ||
+ * (LEFT.y == RIGHT.y && LEFT.x == RIGHT.x && LEFT.oldInd > RIGHT.oldInd)
+ *
+ * After sorting, not only are the points sorted in _pts, but also in pData. So both arrays
+ * will have same index for the same point.
+ *
+ * @param s The start index.
+ * @param e The end index.
+ */
+ void SortPointsByOldInd(int s, int e);
+
+ // fonctions annexes pour ConvertToShape et Booleen
+
+ /**
+ * Prepare point data cache, edge data cache and sweep source cache.
+ */
+ void ResetSweep(); // allocates sweep structures
+
+ /**
+ * Clear point data cache, edge data cache and sweep source cache.
+ */
+ void CleanupSweep(); // deallocates them
+
+ // edge sorting function
+
+ /**
+ * Sort edges given in a list.
+ *
+ * Swapping is done in place so the original list will be modified to a sorted one.
+ *
+ * Edges between (inclusive) edges[s] and edges[e] are all sorted.
+ *
+ * @param edges The list of edges to sort.
+ * @param s The index of the beginning of the list to sort.
+ * @param s The index of the end of the list to sort.
+ */
+ void SortEdgesList(edge_list *edges, int s, int e);
+
+ /**
+ * Test if there is an intersection of an edge on a particular side.
+ *
+ * The actual intersection checking is performed by the other TesteIntersection and this function
+ * calls it, creating an intersection event if an intersection is detected.
+ *
+ * @param t The pointer to the node of the edge whose intersection we wanna test.
+ * @param s The side that we want to test for intersection. If RIGHT, the edge on the right is tested with this one. If LEFT, the edge on the
+ * left is tested with this one.
+ * @param onlyDiff My best guess about onlyDiff is it stands for "only different". Only detect intersections if
+ * both edges come from different shapes, otherwise don't bother.
+ */
+ void TesteIntersection(SweepTree *t, Side s, bool onlyDiff); // test if there is an intersection
+
+ /**
+ * Test intersection between the two edges.
+ *
+ * This is the function that does the actual checking.
+ *
+ * An important point to remember is that left and right aren't just two names for
+ * the edges, that's how the edges should be in the sweepline at the moment, otherwise, the
+ * intersection won't be detected.
+ *
+ * @image html livarot-images/teste-intersection.svg
+ *
+ * This is a very special point as it prevents detecting an intersection that has already passed. See
+ * when an intersection has already passed, the order of nodes in the sweepline have switched, thus the
+ * function won't detect the intersection.
+ *
+ * @image html livarot-images/intersection-cross-product.svg
+ *
+ * @image html livarot-images/intersection-cross-products.svg
+ *
+ * @image html livarot-images/problematic-intersection-case.svg
+ *
+ * This picture is related to the intersection point calculation formulas:
+ *
+ * @image html livarot-images/intersection-point-calculation.svg
+ *
+ * \f[ |\vec{slDot}| = |\vec{left}||\vec{sl}|\sin\theta_{sl}\f]
+ * \f[ |\vec{elDot}| = |\vec{left}||\vec{el}|\sin\theta_{el}\f]
+ *
+ * These cross products (weirdly named) do have a direction too but you need to figure that out
+ * with your fingers. These here only give us the magnitude, however the actual variables in code also have
+ * a positive or negative sign depending on the direction. Index finger of right hand to the first vector,
+ * middle finger to the second vector and if thumb points out of page, cross product is negative, if it
+ * points in the page, cross product is positive. From figure 2 you can already guess that \f$ slDot < 0\f$
+ * and \f$ elDot > 0 \f$. So let's rewrite the formula in the code while taking into account these signs.
+ *
+ * \f[ \vec{atx} = \frac{-|\vec{slDot}|*\vec{rEn} -|\vec{elDot}|*\vec{rSt}}{-|\vec{slDot}|-|\vec{elDot}|}\f]
+ * You can cancel out all the minus signs:
+ * \f[ \vec{atx} = \frac{|\vec{slDot}|*\vec{rEn} + |\vec{elDot}|*\vec{rSt}}{|\vec{slDot}|+|\vec{elDot}|}\f]
+ * \f[ \vec{atx} = \frac{|\vec{left}||\vec{sl}||\sin\theta_{sl}|*\vec{rEn} + |\vec{left}||\vec{el}||\sin\theta_{el}|*\vec{rSt}}{|\vec{left}||\vec{sl}||\sin\theta_{sl}| + |\vec{left}||\vec{el}||\sin\theta_{el}|} \f]
+ * We can cancel the left and we are left with (no word twisting intended):
+ * \f[ \vec{atx} = \frac{|\vec{sl}||\sin\theta_{sl}|*\vec{rEn} + |\vec{el}||\sin\theta_{el}|*\vec{rSt}}{||\vec{sl}||\sin\theta_{sl}| + |\vec{el}||\sin\theta_{el}|} \f]
+ *
+ * \f[ \vec{atx} = \frac{|\vec{sl}||\sin\theta_{sl}|}{|\vec{sl}||\sin\theta_{sl}|+|\vec{el}||\sin\theta_{el}|}*\vec{rEn} + \frac{|\vec{el}||\sin\theta_{el}|}{|\vec{sl}||\sin\theta_{sl}|+|\vec{el}||\sin\theta_{el}|}*\vec{rSt} \f]
+ *
+ * What you see here is a simple variant of the midpoint formula that can give us the intersection point. The sin terms when combined with sl or el
+ * are simply the perpendiculars you see in figure 2 and 3. See how the perpendiculars' relative length change as the intersection point changes on
+ * the right edge? This is exactly the mechanism used to find out the intersection point and its time on each edge. Look at figure 3, the point I'm
+ * trying to make is that the red perpendicular's length divided by sum of length of both red and blue perpendiculars is the same factor as the
+ * (length of the part of the right edge that's to the "right" of intersection) divided by total length of right edge. These ratios are exactly
+ * what we use to find the intersection point as well as the time of these intersection points.
+ *
+ * @param iL Pointer to the left edge's node.
+ * @param iR Pointer to the right edge's node.
+ * @param atx The point of intersection. The function sets this if an intersection was detected.
+ * @param atL The time on the left edge at the intersection point.
+ * @param atR The time on the right edge at the intersection point.
+ * @param onlyDiff My best guess about onlyDiff is it stands for "only different". Only detect intersections if
+ * both edges come from different shapes, otherwise don't bother.
+ *
+ * @return true if intersection detected, otherwise false.
+ */
+ bool TesteIntersection(SweepTree *iL, SweepTree *iR, Geom::Point &atx, double &atL, double &atR, bool onlyDiff);
+ bool TesteIntersection(Shape *iL, Shape *iR, int ilb, int irb,
+ Geom::Point &atx, double &atL, double &atR,
+ bool onlyDiff);
+ bool TesteAdjacency(Shape *iL, int ilb, const Geom::Point atx, int nPt,
+ bool push);
+ int PushIncidence(Shape *a, int cb, int pt, double theta);
+ int CreateIncidence(Shape *a, int cb, int pt);
+ void AssemblePoints(Shape *a);
+
+ /**
+ * Sort the points and merge duplicate ones.
+ *
+ * Sort the points from st to en - 1. No idea why the parameters were
+ * set up in this weird way.
+ *
+ * The function also sets up newInd to the new indices so everything else
+ * can update the indices instantly.
+ *
+ * @param st The start of the range of points to sort.
+ * @param en One more than the end of the range of points to sort.
+ *
+ * @return If st and en are the same, nothing is done and en is returned. Otherwise, an index one more than the last point
+ * in the sorted and merged list is returned. So say we gave the sequence of points indexed 2,4,5,6,7 and 4 and 5 were duplicates
+ * so final sequence would have indices 2,4,5,6 and the function will return 7.
+ */
+ int AssemblePoints(int st, int en);
+ void AssembleAretes(FillRule directed = fill_nonZero);
+
+ /**
+ * Add the event in chgts.
+ *
+ * The function adds stuff to the edgehead and shapeHead linked lists as well as set the
+ * leftRnd and rightRnd in swsData of the edges.
+ *
+ * @image html livarot-images/situation-from-add-chgt.svg
+ *
+ * @param lastPointNo The point that was just added in "this" shape.
+ * @param lastChgtPt Either lastPointNo if it's the left most point at that y level or whichever point is the left most at the
+ * same y level as lastPointNo.
+ * @param shapeHead The linked list from ConvertToShape and Booleen.
+ * @param edgeHead The linked list from ConvertToShape and Booleen.
+ * @param type The type of event this is.
+ * @param lS Pointer to the unique edge's shape if this is edge addition/removal or the left edge's shape if an intersection event.
+ * @param lB The unique edge (or the left edge if an intersection event).
+ * @param rS Pointer to the right edge's shape if this is an intersection event.
+ * @param rB The right edge if this is an intersection event.
+ */
+ void AddChgt(int lastPointNo, int lastChgtPt, Shape *&shapeHead,
+ int &edgeHead, sTreeChangeType type, Shape *lS, int lB, Shape *rS,
+ int rB);
+
+ /**
+ * If there are points that lie on edges, mark this by modifying leftRnd and rightRnd variables.
+ *
+ * Please note that an adjacency means a point lying on an edge somewhere. This is checked by
+ * the function TesteAdjacency.
+ *
+ * Before I go into discussing how it works please review the following figure to have an idea
+ * about how the points look like when this function is called from Shape::ConvertToShape.
+ *
+ * @image html livarot-images/lastChgtPt-from-avance.svg
+ *
+ * This function has a main loop that runs on all events in chgts. Each event can either be
+ * an edge addition or edge addition or edge intersection. Each event (chgt) keeps four important
+ * things. A unique edge associated with the event. For addition/removal it's the main edge that got
+ * added/removed. For intersection it's the left one. It stores the right edge too in case of an
+ * intersection. Then there are two pointers to edges to the left and right in the sweepline at the time
+ * the event happened. This function does four things:
+ *
+ * 1. For the unique edge, get the leftRnd and rightRnd. Sets chLeN and chRiN depending on ptNo of the
+ * event and the leftRnd and rightRnd. See the code to see how this is done. We test all points ranging
+ * from lastChgtPt to leftRnd-1 for a possible adjacency with this unique edge. If found, we set leftRnd
+ * of the unique edge accordingly. We also test points in the range rightRnd+1 to lastPointNo (not included)
+ * for an adjacency and if found, we set rightRnd accordingly.
+ * 2. Exactly identical thing is done with the right edge (if this is an intersection event).
+ * 3. Then there is the possibility of having an edge on the left in the sweepline at the time the event happened.
+ * If that's the case, we do something very special. We check if the left edge's leftRnd is smaller than lastChgtPt,
+ * if not, it means the leftRnd got updated in the previous iteration of the main loop, thus, we don't need to take
+ * care of it or do anything about it. If it is smaller than lastChgtPt, we run a loop testing all points in the range
+ * chLeN..chRiN of having an adjacency with that edge. We update leftRnd and rightRnd of the left edge after doing some
+ * checks. This process gets repeated for all the edges to the left.
+ * 4. Same as 3 but for edges to the right.
+ *
+ * 3 and 4 are very useful. They deal with cases when you have some other edge's endpoint exactly on some edge and these
+ * modify leftRnd and rightRnd of the edge so that CheckEdges will later split that edge at that endpoint. For example,
+ * a shape like:
+ * SVG path: M 500,200 L 500,800 L 200,800 L 500,500 L 200,200 Z
+ *
+ */
+ void CheckAdjacencies(int lastPointNo, int lastChgtPt, Shape *shapeHead, int edgeHead);
+
+ /**
+ * Check if there are edges to draw and draw them.
+ *
+ * @image html livarot-images/lastChgtPt-from-avance.svg
+ *
+ * @param lastPointNo The point that was just added. See the figure above.
+ * @param lastChgtPt See the figure above.
+ * @param a The main shape a.
+ * @param b The other shape if called from Shape::Booleen.
+ * @param mod The boolean operation mode if called from Shape::Booleen.
+ */
+ void CheckEdges(int lastPointNo, int lastChgtPt, Shape *a, Shape *b, BooleanOp mod);
+
+ /**
+ * Do the edge.
+ *
+ * That's a very vague thing to say but basically this function checks the leftRnd and rightRnd
+ * of the edge and if anything needs to be drawn, it draws them.
+ *
+ * Most of the code you see in the function body deals with a very rare diagonal case that I suspect would be
+ * extremely rare. I'll add comments in the code body to further highlight this. But if you ignore all of that
+ * whatever remains is quite simple.
+ *
+ * @image html livarot-images/lastChgtPt-from-avance.svg
+ *
+ * This picture shows you how the variables look like when Avance is called by CheckEdges which is called from
+ * a block of code in Shape::ConvertToShape or Shape::Booleen. You can see that lastPointNo is the point that
+ * just got added to the list and it has to be the left most, there can't be another point at the same y and
+ * to the left of lastPointNo. The reason is, the block which calls CheckEdges is called at the leftmost point
+ * at a particular y. You should also note how lastChgtPt is the left most point but just above lastPointNo
+ * (smaller y), can't be the same y.
+ *
+ * @image html livarot-images/rounded-edge-diagonal-avoid.svg
+ *
+ * This image is referred from code comments to help explain a point.
+ *
+ * @param lastPointNo The new point that the sweepline just jumped on. No edge processing (adding/removal) has
+ * been done on the point yet. The point has only been added in "this" shape and this is its index.
+ * @param lastChgtPt This is hard to visualize but imagine the set of points having a y just smaller than lastPointNo's y
+ * and now within those points (if there are multiple ones), get the left most one. That's what lastChgtPt will be.
+ * @param iS The shape to which edge iB belongs.
+ * @param iB The index of the edge to draw/do.
+ * @param a Shape a. Not really used.
+ * @param b Shape b if called ultimately from Shape::Booleen.
+ * @param mod The mode of boolean operation.
+ */
+ void Avance(int lastPointNo, int lastChgtPt, Shape *iS, int iB, Shape *a, Shape *b, BooleanOp mod);
+
+ /**
+ * Draw edge to a passed point.
+ *
+ * You might ask, don't we need two points to draw an edge. Well iB is the original edge
+ * passed. The edge stores the last point until which it was drawn. We will simply draw
+ * an edge between that last point and the point iTo.
+ *
+ * Say that lp is the last point drawn and iTo is the new point. The edge will be drawn
+ * in the following fashion depending on the values of direct and sens
+ *
+ * direct & sens : lp -> iTo
+ * !direct & sens : iTo -> lp
+ * direct & !sens : iTo -> lp
+ * !direct & !sens : lp -> iTo
+ *
+ * If the edges had back data, the backdata for the new points is carefully calculated by doing
+ * some maths so the correct t values are found. The function also updates "last point drawn" of
+ * that edge to the point iTo. There is one more important thing that this function does. If the
+ * edge iB has a linked list of points associated with it (due to computation of seed winding numbers)
+ * then we make sure to transfer that linked list of points to the new edge that we just drew and
+ * destroy the association with the original edge iB.
+ *
+ * @param iS The shape to which the original edge belongs.
+ * @param iB The original edge in shape iS.
+ * @param iTo The point to draw the edge to.
+ * @param direct Used to control direction of the edge.
+ * @param sens Used to control direction of the edge.
+ */
+ void DoEdgeTo(Shape *iS, int iB, int iTo, bool direct, bool sens);
+
+ /**
+ * Calculates the winding numbers to the left and right of all edges of this shape.
+ *
+ * The winding numbers that are calculated are stored in swdData.
+ *
+ * The winding number computation algorithm is a very interesting one and I'll get into
+ * its details too. The algorithm essentially follows sub-graphs to calculate winding
+ * numbers. A sub-graph is basically a set of edges and points that are connected to each
+ * other in the sense that you can walk on the edges to move around them. The winding number
+ * computation algorithm starts at the top most (and left most if there are multiple points at same y).
+ * There, it is known that the winding number outside the edges is definitely 0 since it's the outer most
+ * point. However, when you are starting on an edge that's inside the shape, a rectangle inside another one,
+ * you need to know what outside winding number really is for that point. See the following image to see
+ * what I mean.
+ *
+ * @image html livarot-images/winding-computation-seed.svg
+ *
+ * For the outer contour, we know it's definitely 0 but for the inside one it needs to be calculated and it's -1.
+ * There are two ways to figure out this "seed" winding number. You can either iterate through all edges and calculate
+ * it manually. This is known as the brutual method. The other method is to use the winding number info left from the
+ * sweepline algorithm.
+ *
+ * I have explained the winding number computation algorithm in detail in the code comments. Once we have a seed, we start
+ * walking on the edges. Once you have the left and right winding number for the first edge, you can move to its endpoint
+ * and depending on the relative directions of the edge vectors, you can definitely calculate the winding number for the
+ * next edge. See the code comments for details on this procedure.
+ *
+ * @image html livarot-images/winding-computation.svg
+ *
+ * Basically, given the winding numbers to the left and right of the current edge, and the orientation of the next edge
+ * with this one, we can calculate the winding number of the next edge and then repeat this process.
+ *
+ * @param a Useless.
+ * @param b Useless.
+ * @param mod Useless.
+ * @param brutal Should the algorithm use winding number seeds left by the sweepline or brutually compute the seeds?
+ */
+ void GetWindings(Shape *a, Shape *b = nullptr, BooleanOp mod = bool_op_union, bool brutal = false);
+
+ void Validate();
+
+ /**
+ * Get the winding number for a point but from the data left by the sweepline algorithm.
+ *
+ * @param nPt Index of the point whose finding number we wanna calculate.
+ */
+ int Winding(int nPt) const;
+
+ /**
+ * Sort all points by their rounded coordinates.
+ */
+ void SortPointsRounded();
+
+ /**
+ * Sort points by their rounded coordinates.
+ *
+ * Exactly the same as SortPoints but instead of using the actual point coordinates
+ * rounded coordinates are used.
+ *
+ * @param s The start index.
+ * @param e The end index.
+ */
+ void SortPointsRounded(int s, int e);
+
+ void CreateEdge(int no, float to, float step);
+ void AvanceEdge(int no, float to, bool exact, float step);
+ void DestroyEdge(int no, float to, FloatLigne *line);
+ void AvanceEdge(int no, float to, FloatLigne *line, bool exact, float step);
+ void DestroyEdge(int no, BitLigne *line);
+ void AvanceEdge(int no, float to, BitLigne *line, bool exact, float step);
+ void DestroyEdge(int no, AlphaLigne *line);
+ void AvanceEdge(int no, float to, AlphaLigne *line, bool exact, float step);
+
+ /**
+ * Add a contour.
+ *
+ * @param dest The pointer to the Path object where we want to add contours.
+ * @param nbP The total number of path object points in the array orig.
+ * @param orig A pointer of Path object pointers. These are the original Path objects which were used to fill the directed graph.
+ * @param startBord The first edge in the contour.
+ * @param curBord The last edge in the contour.
+ * @param splitWhenForced TODO: No idea what it does. We never use ForcedPoints in Inkscape so doesn't matter I think.
+ */
+ void AddContour(Path * dest, int nbP, Path **orig, int startBord,
+ int curBord, bool splitWhenForced);
+
+ int ReFormeLineTo(int bord, int curBord, Path *dest, Path *orig);
+ int ReFormeArcTo(int bord, int curBord, Path *dest, Path *orig);
+ int ReFormeCubicTo(int bord, int curBord, Path *dest, Path *orig);
+ int ReFormeBezierTo(int bord, int curBord, Path *dest, Path *orig);
+ void ReFormeBezierChunk(const Geom::Point px, const Geom::Point nx,
+ Path *dest, int inBezier, int nbInterm,
+ Path *from, int p, double ts, double te);
+
+ int QuickRasterChgEdge(int oBord, int nbord, double x);
+ int QuickRasterAddEdge(int bord, double x, int guess);
+ void QuickRasterSubEdge(int bord);
+ void QuickRasterSwapEdge(int a, int b);
+ void QuickRasterSort();
+
+ bool _need_points_sorting; ///< points have been added or removed: we need to sort the points again
+ bool _need_edges_sorting; ///< edges have been added: maybe they are not ordered clockwise
+ ///< nota: if you remove an edge, the clockwise order still holds
+ bool _has_points_data; ///< the pData array is allocated
+ bool _point_data_initialised;///< the pData array is up to date
+ bool _has_edges_data; ///< the eData array is allocated
+ bool _has_sweep_src_data; ///< the swsData array is allocated
+ bool _has_sweep_dest_data; ///< the swdData array is allocated
+ bool _has_raster_data; ///< the swrData array is allocated
+ bool _has_quick_raster_data;///< the swrData array is allocated
+ bool _has_back_data; //< the ebData array is allocated
+ bool _has_voronoi_data;
+ bool _bbox_up_to_date; ///< the leftX/rightX/topY/bottomY are up to date
+
+ std::vector<dg_point> _pts; /*!< The array of points */
+ std::vector<dg_arete> _aretes; /*!< The array of edges */
+
+ // the arrays of temporary data
+ // these ones are dynamically kept at a length of maxPt or maxAr
+ std::vector<edge_data> eData; /*!< Extra edge data */
+ std::vector<sweep_src_data> swsData;
+ std::vector<sweep_dest_data> swdData;
+ std::vector<raster_data> swrData;
+ std::vector<point_data> pData; /*!< Extra point data */
+
+ static int CmpQRs(const quick_raster_data &p1, const quick_raster_data &p2) {
+ if ( fabs(p1.x - p2.x) < 0.00001 ) {
+ return 0;
+ }
+
+ return ( ( p1.x < p2.x ) ? -1 : 1 );
+ };
+
+ // edge direction comparison function
+
+ /**
+ * Edge comparison function.
+ *
+ * The function returns +1 when a swap is needed. The arguments
+ * are arranged in a weird way. Say you have two edges: w x y z and you wanna ask
+ * if w and x should be swapped, you pass parameters such that ax = x & bx = w.
+ *
+ * The explaination of the function in the code body uses this picture to help explain.
+ *
+ * @image html livarot-images/edge-sorting.svg
+ *
+ * @param ax The right edge in the list before sorting.
+ * @param bx The left edge in the list before sorting.
+ * @param as True if the edge of vector ax started from the point. False if it ended there.
+ * @param bs True if the edge of vector bx started from the point. False if it ended there.
+ *
+ * @return A positive number if the arrangement bx ax is wrong and should be swapped.
+ */
+ static int CmpToVert(const Geom::Point ax, const Geom::Point bx, bool as, bool bs);
+};
+
+/**
+ * Is the graph Eulerian?
+ *
+ * A directed graph is Eulerian if every vertex has equal indegree and outdegree.
+ * http://mathworld.wolfram.com/EulerianGraph.html
+ *
+ * @param s Pointer to the shape object.
+ * @return True if shape is Eulerian.
+ */
+bool directedEulerian(Shape const *s);
+double distance(Shape const *s, Geom::Point const &p);
+bool distanceLessThanOrEqual(Shape const *s, Geom::Point const &p, double const max_l2);
+
+#endif
diff --git a/src/livarot/ShapeDraw.cpp b/src/livarot/ShapeDraw.cpp
new file mode 100644
index 0000000..79d070b
--- /dev/null
+++ b/src/livarot/ShapeDraw.cpp
@@ -0,0 +1,114 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * TODO: insert short description here
+ *//*
+ * Authors: see git history
+ *
+ * Copyright (C) 2018 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+/*
+ * ShapeDraw.cpp
+ * nlivarot
+ *
+ * Created by fred on Mon Jun 16 2003.
+ *
+ */
+
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+#include "Shape.h"
+//#include <ApplicationServices/ApplicationServices.h>
+
+// debug routine for vizualizing the polygons
+void
+Shape::Plot (double ix, double iy, double ir, double mx, double my, bool doPoint,
+ bool edgesNo, bool pointsNo, bool doDir,char* fileName)
+{
+ FILE* outFile=fopen(fileName,"w+");
+// fprintf(outFile,"\n\n\n");
+ fprintf(outFile,"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n");
+ fprintf(outFile,"<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.0//EN\"\n");
+ fprintf(outFile,"\"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd\">\n");
+ fprintf(outFile,"<svg:svg\n");
+ fprintf(outFile," id=\"svg1\"\n");
+ fprintf(outFile," inkscape:version=\"0.38cvs\"\n");
+ fprintf(outFile," xmlns:svg=\"http://www.w3.org/2000/svg\"\n");
+ fprintf(outFile," xmlns:sodipodi=\"http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd\"\n");
+ fprintf(outFile," xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\"\n");
+ fprintf(outFile," xmlns:xlink=\"http://www.w3.org/1999/xlink\"\n");
+ fprintf(outFile," width=\"210mm\"\n");
+ fprintf(outFile," height=\"297mm\"\n");
+ fprintf(outFile," sodipodi:docbase=\"/Volumes/Sancho/inkscapecvs\"\n");
+ fprintf(outFile," sodipodi:docname=\"/Volumes/Sancho/inkscapecvs/modele.svg\">\n");
+ fprintf(outFile," <svg:defs\n");
+ fprintf(outFile," id=\"defs3\" />\n");
+ fprintf(outFile," <sodipodi:namedview\n");
+ fprintf(outFile," id=\"base\"\n");
+ fprintf(outFile," pagecolor=\"#ffffff\"\n");
+ fprintf(outFile," bordercolor=\"#666666\"\n");
+ fprintf(outFile," borderopacity=\"1.0\"\n");
+ fprintf(outFile," inkscape:pageopacity=\"0.0\"\n");
+ fprintf(outFile," inkscape:pageshadow=\"2\"\n");
+ fprintf(outFile," inkscape:zoom=\"0.43415836\"\n");
+ fprintf(outFile," inkscape:cx=\"305.25952637\"\n");
+ fprintf(outFile," inkscape:cy=\"417.84947271\"\n");
+ fprintf(outFile," inkscape:window-width=\"640\"\n");
+ fprintf(outFile," inkscape:window-height=\"496\"\n");
+ fprintf(outFile," inkscape:window-x=\"20\"\n");
+ fprintf(outFile," inkscape:window-y=\"42\" />\n");
+
+ if ( doPoint ) {
+ for (int i=0;i<numberOfPoints();i++) {
+ double ph=(getPoint(i).x[0]-ix)*ir+mx;
+ double pv=(getPoint(i).x[1]-iy)*ir+my;
+ fprintf(outFile," <svg:circle cx=\"%f\" cy=\"%f\" r=\"5\" fill=\"none\" stroke=\"red\" stroke-width=\"0.25\" />\n",ph,pv); // localizing ok
+ }
+ }
+ if ( pointsNo ) {
+ for (int i=0;i<numberOfPoints();i++) {
+ double ph=(getPoint(i).x[0]-ix)*ir+mx;
+ double pv=(getPoint(i).x[1]-iy)*ir+my;
+ fprintf(outFile," <svg:text x=\"%f\" y=\"%f\" font-family=\"Monaco\" font-size=\"5\" fill=\"blue\" >\n",ph-2,pv+1); // localizing ok
+ fprintf(outFile,"%i\n",i);
+ fprintf(outFile," </text>\n");
+ }
+ }
+ {
+ for (int i=0;i<numberOfEdges();i++) {
+ int stP=getEdge(i).st;
+ int enP=getEdge(i).en;
+ if ( stP < 0 || enP < 0 ) continue;
+ double sh=(getPoint(stP).x[0]-ix)*ir+mx;
+ double sv=(getPoint(stP).x[1]-iy)*ir+my;
+ double eh=(getPoint(enP).x[0]-ix)*ir+mx;
+ double ev=(getPoint(enP).x[1]-iy)*ir+my;
+ if ( doDir ) {
+ double endh=(9*eh+1*sh)/10;
+ double endv=(9*ev+1*sv)/10;
+ fprintf(outFile," <svg:line x1=\"%f\" y1=\"%f\" x2=\"%f\" y2=\"%f\" stroke=\"black\" stroke-width=\"0.5\" />\n",sh,sv,endh,endv); // localizing ok
+ } else {
+ fprintf(outFile," <svg:line x1=\"%f\" y1=\"%f\" x2=\"%f\" y2=\"%f\" stroke=\"black\" stroke-width=\"0.5\" />\n",sh,sv,eh,ev); // localizing ok
+ }
+ }
+ }
+ if ( edgesNo ) {
+ for (int i=0;i<numberOfEdges();i++) {
+ int stP=getEdge(i).st;
+ int enP=getEdge(i).en;
+ if ( stP < 0 || enP < 0 ) continue;
+ double sh=(getPoint(stP).x[0]-ix)*ir+mx;
+ double sv=(getPoint(stP).x[1]-iy)*ir+my;
+ double eh=(getPoint(enP).x[0]-ix)*ir+mx;
+ double ev=(getPoint(enP).x[1]-iy)*ir+my;
+ fprintf(outFile," <svg:text x=\"%f\" y=\"%f\" font-family=\"Monaco\" font-size=\"5\" fill=\"blue\" >\n",(sh+eh)/2+2,(sv+ev)/2); // localizing ok
+ fprintf(outFile,"%i\n",i);
+ fprintf(outFile," </text>\n");
+ }
+ }
+
+ fprintf(outFile,"</svg>\n");
+ fclose(outFile);
+
+}
diff --git a/src/livarot/ShapeMisc.cpp b/src/livarot/ShapeMisc.cpp
new file mode 100644
index 0000000..36e3a23
--- /dev/null
+++ b/src/livarot/ShapeMisc.cpp
@@ -0,0 +1,1476 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * TODO: insert short description here
+ *//*
+ * Authors:
+ * see git history
+ * Fred
+ *
+ * Copyright (C) 2018 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "livarot/Shape.h"
+#include "livarot/Path.h"
+#include "livarot/path-description.h"
+#include <glib.h>
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+#include <2geom/point.h>
+#include <2geom/affine.h>
+
+/*
+ * polygon offset and polyline to path reassembling (when using back data)
+ */
+
+// until i find something better
+#define MiscNormalize(v) {\
+ double _l=sqrt(dot(v,v)); \
+ if ( _l < 0.0000001 ) { \
+ v[0]=v[1]=0; \
+ } else { \
+ v/=_l; \
+ }\
+}
+
+// extracting the contour of an uncrossed polygon: a mere depth first search
+// more precisely that's extracting an eulerian path from a graph, but here we want to split
+// the polygon into contours and avoid holes. so we take a "next counter-clockwise edge first" approach
+// (make a checkboard and extract its contours to see the difference)
+void
+Shape::ConvertToForme (Path * dest)
+{
+ // this function is quite similar to Shape::GetWindings so please check it out
+ // first to learn the overall technique and I'll make sure to comment the parts
+ // that are different
+
+ if (numberOfPoints() <= 1 || numberOfEdges() <= 1)
+ return;
+
+ // prepare
+ dest->Reset ();
+
+ MakePointData (true);
+ MakeEdgeData (true);
+ MakeSweepDestData (true);
+
+ for (int i = 0; i < numberOfPoints(); i++)
+ {
+ pData[i].rx[0] = Round (getPoint(i).x[0]);
+ pData[i].rx[1] = Round (getPoint(i).x[1]);
+ }
+ for (int i = 0; i < numberOfEdges(); i++)
+ {
+ eData[i].rdx = pData[getEdge(i).en].rx - pData[getEdge(i).st].rx;
+ }
+
+ // sort edge clockwise, with the closest after midnight being first in the doubly-linked list
+ // that's vital to the algorithm...
+ SortEdges ();
+
+ // depth-first search implies: we make a stack of edges traversed.
+ // precParc: previous in the stack
+ // suivParc: next in the stack
+ for (int i = 0; i < numberOfEdges(); i++)
+ {
+ swdData[i].misc = nullptr;
+ swdData[i].precParc = swdData[i].suivParc = -1;
+ }
+
+ int searchInd = 0;
+
+ int lastPtUsed = 0;
+ do
+ {
+ // first get a starting point, and a starting edge
+ // -> take the upper left point, and take its first edge
+ // points traversed have swdData[].misc != 0, so it's easy
+ int startBord = -1;
+ {
+ int fi = 0;
+ for (fi = lastPtUsed; fi < numberOfPoints(); fi++)
+ {
+ if (getPoint(fi).incidentEdge[FIRST] >= 0 && swdData[getPoint(fi).incidentEdge[FIRST]].misc == nullptr)
+ break;
+ }
+ lastPtUsed = fi + 1;
+ if (fi < numberOfPoints())
+ {
+ int bestB = getPoint(fi).incidentEdge[FIRST];
+ // we get the edge that starts at this point since we wanna follow the direction of the edges
+ while (bestB >= 0 && getEdge(bestB).st != fi)
+ bestB = NextAt (fi, bestB);
+ if (bestB >= 0)
+ {
+ startBord = bestB;
+ dest->MoveTo (getPoint(getEdge(startBord).en).x);
+ }
+ }
+ }
+ // and walk the graph, doing contours when needed
+ if (startBord >= 0)
+ {
+ // parcours en profondeur pour mettre les leF et riF a leurs valeurs
+ swdData[startBord].misc = (void *) 1;
+ // printf("part de %d\n",startBord);
+ int curBord = startBord;
+ bool back = false; // a variable that if true, means we are back tracking
+ swdData[curBord].precParc = -1;
+ swdData[curBord].suivParc = -1;
+ do
+ {
+ int cPt = getEdge(curBord).en; // get the end point, we want to follow the direction of the edge
+ int nb = curBord;
+ // printf("de curBord= %d au point %i -> ",curBord,cPt);
+ // get next edge
+ do
+ {
+ int nnb = CycleNextAt (cPt, nb); // get the next (clockwise) edge (I don't see why the comment at the top says anti clockwise edge first)
+ if (nnb == nb)
+ {
+ // cul-de-sac
+ nb = -1;
+ break;
+ }
+ nb = nnb;
+ if (nb < 0 || nb == curBord) // if we got to the same edge, we break, cuz now we need to back track
+ break;
+ }
+ while (swdData[nb].misc != nullptr || getEdge(nb).st != cPt); // keep finding a new edge until we find an edge that we haven't seen or an edge
+ // that starts at the point
+
+ if (nb < 0 || nb == curBord)
+ {
+ // if we are here, means there was no new edge, so we start back tracking
+ // no next edge: end of this contour, we get back
+ if (back == false)
+ dest->Close ();
+ back = true;
+ // retour en arriere
+ curBord = swdData[curBord].precParc; // set previous edge to current edge and back is true to indicate we are backtracking
+ // printf("retour vers %d\n",curBord);
+ if (curBord < 0) // break if no edge exists before this one
+ break;
+ }
+ else
+ {
+ // did we get this new edge after we started backtracking? if yes, we need a moveTo
+ // new edge, maybe for a new contour
+ if (back)
+ {
+ // we were backtracking, so if we have a new edge, that means we're creating a new contour
+ dest->MoveTo (getPoint(cPt).x);
+ back = false; // we are no longer backtracking, we will follow this new edge now
+ }
+ swdData[nb].misc = (void *) 1;
+ swdData[nb].ind = searchInd++;
+ swdData[nb].precParc = curBord;
+ swdData[curBord].suivParc = nb;
+ curBord = nb;
+ // printf("suite %d\n",curBord);
+ {
+ // add that edge
+ dest->LineTo (getPoint(getEdge(nb).en).x); // add a line segment
+ }
+ }
+ }
+ while (true /*swdData[curBord].precParc >= 0 */ );
+ // fin du cas non-oriente
+ }
+ }
+ while (lastPtUsed < numberOfPoints());
+
+ MakePointData (false);
+ MakeEdgeData (false);
+ MakeSweepDestData (false);
+}
+
+// same as before, but each time we have a contour, try to reassemble the segments on it to make chunks of
+// the original(s) path(s)
+// originals are in the orig array, whose size is nbP
+void
+Shape::ConvertToForme (Path * dest, int nbP, Path * *orig, bool splitWhenForced)
+{
+ // the function is similar to the other version of ConvertToForme, I'm adding comments
+ // where there are significant differences to explain
+ if (numberOfPoints() <= 1 || numberOfEdges() <= 1)
+ return;
+// if (Eulerian (true) == false)
+// return;
+
+ if (_has_back_data == false)
+ {
+ ConvertToForme (dest);
+ return;
+ }
+
+ dest->Reset ();
+
+ MakePointData (true);
+ MakeEdgeData (true);
+ MakeSweepDestData (true);
+
+ for (int i = 0; i < numberOfPoints(); i++)
+ {
+ pData[i].rx[0] = Round (getPoint(i).x[0]);
+ pData[i].rx[1] = Round (getPoint(i).x[1]);
+ }
+ for (int i = 0; i < numberOfEdges(); i++)
+ {
+ eData[i].rdx = pData[getEdge(i).en].rx - pData[getEdge(i).st].rx;
+ }
+
+ SortEdges ();
+
+ for (int i = 0; i < numberOfEdges(); i++)
+ {
+ swdData[i].misc = nullptr;
+ swdData[i].precParc = swdData[i].suivParc = -1;
+ }
+
+ int searchInd = 0;
+
+ int lastPtUsed = 0;
+ do
+ {
+ int startBord = -1;
+ {
+ int fi = 0;
+ for (fi = lastPtUsed; fi < numberOfPoints(); fi++)
+ {
+ if (getPoint(fi).incidentEdge[FIRST] >= 0 && swdData[getPoint(fi).incidentEdge[FIRST]].misc == nullptr)
+ break;
+ }
+ lastPtUsed = fi + 1;
+ if (fi < numberOfPoints())
+ {
+ int bestB = getPoint(fi).incidentEdge[FIRST];
+ while (bestB >= 0 && getEdge(bestB).st != fi)
+ bestB = NextAt (fi, bestB);
+ if (bestB >= 0)
+ {
+ startBord = bestB; // no moveTo here unlike the other ConvertToForme because we want AddContour to do all the contour extraction stuff
+ }
+ }
+ }
+ if (startBord >= 0)
+ {
+ // parcours en profondeur pour mettre les leF et riF a leurs valeurs
+ swdData[startBord].misc = (void *) 1;
+ //printf("part de %d\n",startBord);
+ int curBord = startBord;
+ bool back = false;
+ swdData[curBord].precParc = -1;
+ swdData[curBord].suivParc = -1;
+ int curStartPt=getEdge(curBord).st; // we record the start point of the current edge (actually the start edge at the moment)
+ do
+ {
+ int cPt = getEdge(curBord).en;
+ int nb = curBord;
+ //printf("de curBord= %d au point %i -> ",curBord,cPt);
+ do
+ {
+ int nnb = CycleNextAt (cPt, nb);
+ if (nnb == nb)
+ {
+ // cul-de-sac
+ nb = -1;
+ break;
+ }
+ nb = nnb;
+ if (nb < 0 || nb == curBord) // if we get the same edge, we break
+ break;
+ }
+ while (swdData[nb].misc != nullptr || getEdge(nb).st != cPt);
+
+ if (nb < 0 || nb == curBord)
+ { // we are backtracking
+ if (back == false) // if we weren't backtracking
+ {
+ if (curBord == startBord || curBord < 0) // is the current edge the one we started on?
+ {
+ // probleme -> on vire le moveto
+ // dest->descr_nb--;
+ }
+ else // if not, we now have a contour to add
+ {
+ swdData[curBord].suivParc = -1;
+ AddContour (dest, nbP, orig, startBord, curBord,splitWhenForced);
+ }
+ // dest->Close();
+ }
+ back = true; // we are backtracking now
+ // retour en arriere
+ curBord = swdData[curBord].precParc; // get the previous edge
+ //printf("retour vers %d\n",curBord);
+ if (curBord < 0) // if no previous edge, we break
+ break;
+ }
+ else
+ {
+ if (back)
+ { // if we are backtracking, now we go forward (since we have found a new edge to explore)
+ back = false;
+ startBord = nb;
+ curStartPt=getEdge(nb).st; // reset curStartPt to this as a new contour is starting here
+ } else {
+ if ( getEdge(curBord).en == curStartPt ) { // if we are going forward and the endpoint of this edge is the actual start point
+ //printf("contour %i ",curStartPt);
+ swdData[curBord].suivParc = -1; // why tho? seems useless since we set it right after this block ends
+ AddContour (dest, nbP, orig, startBord, curBord,splitWhenForced); // add the contour
+ startBord=nb; // set startBord to this edge
+ }
+ }
+ swdData[nb].misc = (void *) 1;
+ swdData[nb].ind = searchInd++;
+ swdData[nb].precParc = curBord;
+ swdData[curBord].suivParc = nb;
+ curBord = nb;
+ //printf("suite %d\n",curBord);
+ }
+ }
+ while (true /*swdData[curBord].precParc >= 0 */ );
+ // fin du cas non-oriente
+ }
+ }
+ while (lastPtUsed < numberOfPoints());
+
+ MakePointData (false);
+ MakeEdgeData (false);
+ MakeSweepDestData (false);
+}
+void
+Shape::ConvertToFormeNested (Path * dest, int nbP, Path * *orig, int /*wildPath*/,int &nbNest,int *&nesting,int *&contStart,bool splitWhenForced)
+{
+ nesting=nullptr;
+ contStart=nullptr;
+ nbNest=0;
+
+ if (numberOfPoints() <= 1 || numberOfEdges() <= 1)
+ return;
+ // if (Eulerian (true) == false)
+ // return;
+
+ if (_has_back_data == false)
+ {
+ ConvertToForme (dest);
+ return;
+ }
+
+ dest->Reset ();
+
+// MakePointData (true);
+ MakeEdgeData (true);
+ MakeSweepDestData (true);
+
+ for (int i = 0; i < numberOfPoints(); i++)
+ {
+ pData[i].rx[0] = Round (getPoint(i).x[0]);
+ pData[i].rx[1] = Round (getPoint(i).x[1]);
+ }
+ for (int i = 0; i < numberOfEdges(); i++)
+ {
+ eData[i].rdx = pData[getEdge(i).en].rx - pData[getEdge(i).st].rx;
+ }
+
+ SortEdges ();
+
+ for (int i = 0; i < numberOfEdges(); i++)
+ {
+ swdData[i].misc = nullptr;
+ swdData[i].precParc = swdData[i].suivParc = -1;
+ }
+
+ int searchInd = 0;
+
+ int lastPtUsed = 0;
+ int parentContour=-1;
+ do
+ {
+ int childEdge = -1;
+ bool foundChild = false;
+ int startBord = -1;
+ {
+ int fi = 0;
+ for (fi = lastPtUsed; fi < numberOfPoints(); fi++)
+ {
+ if (getPoint(fi).incidentEdge[FIRST] >= 0 && swdData[getPoint(fi).incidentEdge[FIRST]].misc == nullptr)
+ break;
+ }
+ {
+ if (pData.size()<= fi || fi == numberOfPoints()) {
+ parentContour=-1;
+ } else {
+ int askTo = pData[fi].askForWindingB;
+ if (askTo < 0 || askTo >= numberOfEdges() ) {
+ parentContour=-1;
+ } else {
+ if (getEdge(askTo).prevS >= 0) {
+ parentContour = GPOINTER_TO_INT(swdData[askTo].misc);
+ parentContour-=1; // pour compenser le decalage
+ }
+ childEdge = getPoint(fi).incidentEdge[FIRST];
+ }
+ }
+ }
+ lastPtUsed = fi + 1;
+ if (fi < numberOfPoints())
+ {
+ int bestB = getPoint(fi).incidentEdge[FIRST];
+ while (bestB >= 0 && getEdge(bestB).st != fi)
+ bestB = NextAt (fi, bestB);
+ if (bestB >= 0)
+ {
+ startBord = bestB;
+ }
+ }
+ }
+ if (startBord >= 0)
+ {
+ // parcours en profondeur pour mettre les leF et riF a leurs valeurs
+ swdData[startBord].misc = (void *)(intptr_t)(1 + nbNest);
+ if (startBord == childEdge) {
+ foundChild = true;
+ }
+ //printf("part de %d\n",startBord);
+ int curBord = startBord;
+ bool back = false;
+ swdData[curBord].precParc = -1;
+ swdData[curBord].suivParc = -1;
+ int curStartPt=getEdge(curBord).st;
+ do
+ {
+ int cPt = getEdge(curBord).en;
+ int nb = curBord;
+ //printf("de curBord= %d au point %i -> ",curBord,cPt);
+ do
+ {
+ int nnb = CycleNextAt (cPt, nb);
+ if (nnb == nb)
+ {
+ // cul-de-sac
+ nb = -1;
+ break;
+ }
+ nb = nnb;
+ if (nb < 0 || nb == curBord)
+ break;
+ }
+ while (swdData[nb].misc != nullptr || getEdge(nb).st != cPt);
+
+ if (nb < 0 || nb == curBord)
+ {
+ if (back == false)
+ {
+ if (curBord == startBord || curBord < 0)
+ {
+ // probleme -> on vire le moveto
+ // dest->descr_nb--;
+ }
+ else
+ {
+// bool escapePath=false;
+// int tb=curBord;
+// while ( tb >= 0 && tb < numberOfEdges() ) {
+// if ( ebData[tb].pathID == wildPath ) {
+// escapePath=true;
+// break;
+// }
+// tb=swdData[tb].precParc;
+// }
+ nesting=(int*)g_realloc(nesting,(nbNest+1)*sizeof(int));
+ contStart=(int*)g_realloc(contStart,(nbNest+1)*sizeof(int));
+ contStart[nbNest]=dest->descr_cmd.size();
+ if (foundChild) {
+ nesting[nbNest++]=parentContour;
+ foundChild = false;
+ } else {
+ nesting[nbNest++]=-1; // contient des bouts de coupure -> a part
+ }
+ swdData[curBord].suivParc = -1;
+ AddContour (dest, nbP, orig, startBord, curBord,splitWhenForced);
+ }
+ // dest->Close();
+ }
+ back = true;
+ // retour en arriere
+ curBord = swdData[curBord].precParc;
+ //printf("retour vers %d\n",curBord);
+ if (curBord < 0)
+ break;
+ }
+ else
+ {
+ if (back)
+ {
+ back = false;
+ startBord = nb;
+ curStartPt=getEdge(nb).st;
+ } else {
+ if ( getEdge(curBord).en == curStartPt ) {
+ //printf("contour %i ",curStartPt);
+
+// bool escapePath=false;
+// int tb=curBord;
+// while ( tb >= 0 && tb < numberOfEdges() ) {
+// if ( ebData[tb].pathID == wildPath ) {
+// escapePath=true;
+// break;
+// }
+// tb=swdData[tb].precParc;
+// }
+ nesting=(int*)g_realloc(nesting,(nbNest+1)*sizeof(int));
+ contStart=(int*)g_realloc(contStart,(nbNest+1)*sizeof(int));
+ contStart[nbNest]=dest->descr_cmd.size();
+ if (foundChild) {
+ nesting[nbNest++]=parentContour;
+ foundChild = false;
+ } else {
+ nesting[nbNest++]=-1; // contient des bouts de coupure -> a part
+ }
+ swdData[curBord].suivParc = -1;
+ AddContour (dest, nbP, orig, startBord, curBord,splitWhenForced);
+ startBord=nb;
+ }
+ }
+ swdData[nb].misc = (void *)(intptr_t)(1 + nbNest);
+ swdData[nb].ind = searchInd++;
+ swdData[nb].precParc = curBord;
+ swdData[curBord].suivParc = nb;
+ curBord = nb;
+ if (nb == childEdge) {
+ foundChild = true;
+ }
+ //printf("suite %d\n",curBord);
+ }
+ }
+ while (true /*swdData[curBord].precParc >= 0 */ );
+ // fin du cas non-oriente
+ }
+ }
+ while (lastPtUsed < numberOfPoints());
+
+ MakePointData (false);
+ MakeEdgeData (false);
+ MakeSweepDestData (false);
+}
+
+
+int
+Shape::MakeTweak (int mode, Shape *a, double power, JoinType join, double miter, bool do_profile, Geom::Point c, Geom::Point vector, double radius, Geom::Affine *i2doc)
+{
+ Reset (0, 0);
+ MakeBackData(a->_has_back_data);
+
+ bool done_something = false;
+
+ if (power == 0)
+ {
+ _pts = a->_pts;
+ if (numberOfPoints() > maxPt)
+ {
+ maxPt = numberOfPoints();
+ if (_has_points_data) {
+ pData.resize(maxPt);
+ _point_data_initialised = false;
+ _bbox_up_to_date = false;
+ }
+ }
+
+ _aretes = a->_aretes;
+ if (numberOfEdges() > maxAr)
+ {
+ maxAr = numberOfEdges();
+ if (_has_edges_data)
+ eData.resize(maxAr);
+ if (_has_sweep_src_data)
+ swsData.resize(maxAr);
+ if (_has_sweep_dest_data)
+ swdData.resize(maxAr);
+ if (_has_raster_data)
+ swrData.resize(maxAr);
+ if (_has_back_data)
+ ebData.resize(maxAr);
+ }
+ return 0;
+ }
+ if (a->numberOfPoints() <= 1 || a->numberOfEdges() <= 1 || a->type != shape_polygon)
+ return shape_input_err;
+
+ a->SortEdges ();
+
+ a->MakeSweepDestData (true);
+ a->MakeSweepSrcData (true);
+
+ for (int i = 0; i < a->numberOfEdges(); i++)
+ {
+ int stB = -1, enB = -1;
+ if (power <= 0 || mode == tweak_mode_push || mode == tweak_mode_repel || mode == tweak_mode_roughen) {
+ stB = a->CyclePrevAt (a->getEdge(i).st, i);
+ enB = a->CycleNextAt (a->getEdge(i).en, i);
+ } else {
+ stB = a->CycleNextAt (a->getEdge(i).st, i);
+ enB = a->CyclePrevAt (a->getEdge(i).en, i);
+ }
+
+ Geom::Point stD = a->getEdge(stB).dx;
+ Geom::Point seD = a->getEdge(i).dx;
+ Geom::Point enD = a->getEdge(enB).dx;
+
+ double stL = sqrt (dot(stD,stD));
+ double seL = sqrt (dot(seD,seD));
+ //double enL = sqrt (dot(enD,enD));
+ MiscNormalize (stD);
+ MiscNormalize (enD);
+ MiscNormalize (seD);
+
+ Geom::Point ptP;
+ int stNo, enNo;
+ ptP = a->getPoint(a->getEdge(i).st).x;
+
+ Geom::Point to_center = ptP * (*i2doc) - c;
+ Geom::Point to_center_normalized = (1/Geom::L2(to_center)) * to_center;
+
+ double this_power;
+ if (do_profile && i2doc) {
+ double alpha = 1;
+ double x;
+ if (mode == tweak_mode_repel) {
+ x = (Geom::L2(to_center)/radius);
+ } else {
+ x = (Geom::L2(ptP * (*i2doc) - c)/radius);
+ }
+ if (x > 1) {
+ this_power = 0;
+ } else if (x <= 0) {
+ if (mode == tweak_mode_repel) {
+ this_power = 0;
+ } else {
+ this_power = power;
+ }
+ } else {
+ this_power = power * (0.5 * cos (M_PI * (pow(x, alpha))) + 0.5);
+ }
+ } else {
+ if (mode == tweak_mode_repel) {
+ this_power = 0;
+ } else {
+ this_power = power;
+ }
+ }
+
+ if (this_power != 0)
+ done_something = true;
+
+ double scaler = 1 / (*i2doc).descrim();
+
+ Geom::Point this_vec(0,0);
+ if (mode == tweak_mode_push) {
+ Geom::Affine tovec (*i2doc);
+ tovec[4] = tovec[5] = 0;
+ tovec = tovec.inverse();
+ this_vec = this_power * (vector * tovec) ;
+ } else if (mode == tweak_mode_repel) {
+ this_vec = this_power * scaler * to_center_normalized;
+ } else if (mode == tweak_mode_roughen) {
+ double angle = g_random_double_range(0, 2*M_PI);
+ this_vec = g_random_double_range(0, 1) * this_power * scaler * Geom::Point(sin(angle), cos(angle));
+ }
+
+ int usePathID=-1;
+ int usePieceID=0;
+ double useT=0.0;
+ if ( a->_has_back_data ) {
+ if ( a->ebData[i].pathID >= 0 && a->ebData[stB].pathID == a->ebData[i].pathID && a->ebData[stB].pieceID == a->ebData[i].pieceID
+ && a->ebData[stB].tEn == a->ebData[i].tSt ) {
+ usePathID=a->ebData[i].pathID;
+ usePieceID=a->ebData[i].pieceID;
+ useT=a->ebData[i].tSt;
+ } else {
+ usePathID=a->ebData[i].pathID;
+ usePieceID=0;
+ useT=0;
+ }
+ }
+
+ if (mode == tweak_mode_push || mode == tweak_mode_repel || mode == tweak_mode_roughen) {
+ Path::DoLeftJoin (this, 0, join, ptP+this_vec, stD+this_vec, seD+this_vec, miter, stL, seL,
+ stNo, enNo,usePathID,usePieceID,useT);
+ a->swsData[i].stPt = enNo;
+ a->swsData[stB].enPt = stNo;
+ } else {
+ if (power > 0) {
+ Path::DoRightJoin (this, this_power * scaler, join, ptP, stD, seD, miter, stL, seL,
+ stNo, enNo,usePathID,usePieceID,useT);
+ a->swsData[i].stPt = enNo;
+ a->swsData[stB].enPt = stNo;
+ } else {
+ Path::DoLeftJoin (this, -this_power * scaler, join, ptP, stD, seD, miter, stL, seL,
+ stNo, enNo,usePathID,usePieceID,useT);
+ a->swsData[i].stPt = enNo;
+ a->swsData[stB].enPt = stNo;
+ }
+ }
+ }
+
+ if (power < 0 || mode == tweak_mode_push || mode == tweak_mode_repel || mode == tweak_mode_roughen)
+ {
+ for (int i = 0; i < numberOfEdges(); i++)
+ Inverse (i);
+ }
+
+ if ( _has_back_data ) {
+ for (int i = 0; i < a->numberOfEdges(); i++)
+ {
+ int nEd=AddEdge (a->swsData[i].stPt, a->swsData[i].enPt);
+ ebData[nEd]=a->ebData[i];
+ }
+ } else {
+ for (int i = 0; i < a->numberOfEdges(); i++)
+ {
+ AddEdge (a->swsData[i].stPt, a->swsData[i].enPt);
+ }
+ }
+
+ a->MakeSweepSrcData (false);
+ a->MakeSweepDestData (false);
+
+ return (done_something? 0 : shape_nothing_to_do);
+}
+
+
+// offsets
+// take each edge, offset it, and make joins with previous at edge start and next at edge end (previous and
+// next being with respect to the clockwise order)
+// you gotta be very careful with the join, as anything but the right one will fuck everything up
+// see PathStroke.cpp for the "right" joins
+int
+Shape::MakeOffset (Shape * a, double dec, JoinType join, double miter, bool do_profile, double cx, double cy, double radius, Geom::Affine *i2doc)
+{
+ Reset (0, 0);
+ MakeBackData(a->_has_back_data);
+
+ bool done_something = false;
+
+ if (dec == 0)
+ {
+ _pts = a->_pts;
+ if (numberOfPoints() > maxPt)
+ {
+ maxPt = numberOfPoints();
+ if (_has_points_data) {
+ pData.resize(maxPt);
+ _point_data_initialised = false;
+ _bbox_up_to_date = false;
+ }
+ }
+
+ _aretes = a->_aretes;
+ if (numberOfEdges() > maxAr)
+ {
+ maxAr = numberOfEdges();
+ if (_has_edges_data)
+ eData.resize(maxAr);
+ if (_has_sweep_src_data)
+ swsData.resize(maxAr);
+ if (_has_sweep_dest_data)
+ swdData.resize(maxAr);
+ if (_has_raster_data)
+ swrData.resize(maxAr);
+ if (_has_back_data)
+ ebData.resize(maxAr);
+ }
+ return 0;
+ }
+ if (a->numberOfPoints() <= 1 || a->numberOfEdges() <= 1 || a->type != shape_polygon)
+ return shape_input_err;
+
+ a->SortEdges ();
+
+ a->MakeSweepDestData (true);
+ a->MakeSweepSrcData (true);
+
+ for (int i = 0; i < a->numberOfEdges(); i++)
+ {
+ // int stP=a->swsData[i].stPt/*,enP=a->swsData[i].enPt*/;
+ int stB = -1, enB = -1;
+ if (dec > 0)
+ {
+ stB = a->CycleNextAt (a->getEdge(i).st, i);
+ enB = a->CyclePrevAt (a->getEdge(i).en, i);
+ }
+ else
+ {
+ stB = a->CyclePrevAt (a->getEdge(i).st, i);
+ enB = a->CycleNextAt (a->getEdge(i).en, i);
+ }
+
+ Geom::Point stD = a->getEdge(stB).dx;
+ Geom::Point seD = a->getEdge(i).dx;
+ Geom::Point enD = a->getEdge(enB).dx;
+
+ double stL = sqrt (dot(stD,stD));
+ double seL = sqrt (dot(seD,seD));
+ //double enL = sqrt (dot(enD,enD));
+ MiscNormalize (stD);
+ MiscNormalize (enD);
+ MiscNormalize (seD);
+
+ Geom::Point ptP;
+ int stNo, enNo;
+ ptP = a->getPoint(a->getEdge(i).st).x;
+
+ double this_dec;
+ if (do_profile && i2doc) {
+ double alpha = 1;
+ double x = (Geom::L2(ptP * (*i2doc) - Geom::Point(cx,cy))/radius);
+ if (x > 1) {
+ this_dec = 0;
+ } else if (x <= 0) {
+ this_dec = dec;
+ } else {
+ this_dec = dec * (0.5 * cos (M_PI * (pow(x, alpha))) + 0.5);
+ }
+ } else {
+ this_dec = dec;
+ }
+
+ if (this_dec != 0)
+ done_something = true;
+
+ int usePathID=-1;
+ int usePieceID=0;
+ double useT=0.0;
+ if ( a->_has_back_data ) {
+ if ( a->ebData[i].pathID >= 0 && a->ebData[stB].pathID == a->ebData[i].pathID && a->ebData[stB].pieceID == a->ebData[i].pieceID
+ && a->ebData[stB].tEn == a->ebData[i].tSt ) {
+ usePathID=a->ebData[i].pathID;
+ usePieceID=a->ebData[i].pieceID;
+ useT=a->ebData[i].tSt;
+ } else {
+ usePathID=a->ebData[i].pathID;
+ usePieceID=0;
+ useT=0;
+ }
+ }
+ if (dec > 0)
+ {
+ Path::DoRightJoin (this, this_dec, join, ptP, stD, seD, miter, stL, seL,
+ stNo, enNo,usePathID,usePieceID,useT);
+ a->swsData[i].stPt = enNo;
+ a->swsData[stB].enPt = stNo;
+ }
+ else
+ {
+ Path::DoLeftJoin (this, -this_dec, join, ptP, stD, seD, miter, stL, seL,
+ stNo, enNo,usePathID,usePieceID,useT);
+ a->swsData[i].stPt = enNo;
+ a->swsData[stB].enPt = stNo;
+ }
+ }
+
+ if (dec < 0)
+ {
+ for (int i = 0; i < numberOfEdges(); i++)
+ Inverse (i);
+ }
+
+ if ( _has_back_data ) {
+ for (int i = 0; i < a->numberOfEdges(); i++)
+ {
+ int nEd=AddEdge (a->swsData[i].stPt, a->swsData[i].enPt);
+ ebData[nEd]=a->ebData[i];
+ }
+ } else {
+ for (int i = 0; i < a->numberOfEdges(); i++)
+ {
+ AddEdge (a->swsData[i].stPt, a->swsData[i].enPt);
+ }
+ }
+
+ a->MakeSweepSrcData (false);
+ a->MakeSweepDestData (false);
+
+ return (done_something? 0 : shape_nothing_to_do);
+}
+
+
+
+// we found a contour, now reassemble the edges on it, instead of dumping them in the Path "dest" as a
+// polyline. since it was a DFS, the precParc and suivParc make a nice doubly-linked list of the edges in
+// the contour. the first and last edges of the contour are startBord and curBord
+void
+Shape::AddContour (Path * dest, int nbP, Path * *orig, int startBord, int curBord, bool splitWhenForced)
+{
+ int bord = startBord; // start at the first edge
+
+ {
+ dest->MoveTo (getPoint(getEdge(bord).st).x); // do a MoveTo to the starting point
+ }
+
+ while (bord >= 0)
+ {
+ int nPiece = ebData[bord].pieceID; // get the piece and the path ID
+ int nPath = ebData[bord].pathID;
+
+ if (nPath < 0 || nPath >= nbP || orig[nPath] == nullptr) // if the path id is invalid in any way, just add the edge as line segment
+ {
+ // segment batard
+ dest->LineTo (getPoint(getEdge(bord).en).x);
+ bord = swdData[bord].suivParc;
+ }
+ else
+ {
+ Path *from = orig[nPath]; // Get pointer to the path object where this edge comes from
+ if (nPiece < 0 || nPiece >= int(from->descr_cmd.size())) // if the piece id is invalid in anyway, again add the edge as just a line segment
+ {
+ // segment batard
+ dest->LineTo (getPoint(getEdge(bord).en).x);
+ bord = swdData[bord].suivParc;
+ }
+ else
+ {
+ int nType = from->descr_cmd[nPiece]->getType(); // get the type of the description
+ if (nType == descr_close || nType == descr_moveto // if a moveTo, close or forced, we just do a lineTo
+ || nType == descr_forced)
+ {
+ // devrait pas arriver
+ dest->LineTo (getPoint(getEdge(bord).en).x);
+ bord = swdData[bord].suivParc;
+ }
+ else if (nType == descr_lineto) // for the other ones, call the appropriate function, bord gets the return value which tells us the next edge we should look at
+ {
+ bord = ReFormeLineTo (bord, curBord, dest, from);
+ }
+ else if (nType == descr_arcto)
+ {
+ bord = ReFormeArcTo (bord, curBord, dest, from);
+ }
+ else if (nType == descr_cubicto)
+ {
+ bord = ReFormeCubicTo (bord, curBord, dest, from);
+ }
+ else if (nType == descr_bezierto)
+ {
+ PathDescrBezierTo* nBData =
+ dynamic_cast<PathDescrBezierTo *>(from->descr_cmd[nPiece]);
+
+ if (nBData->nb == 0)
+ {
+ bord = ReFormeLineTo (bord, curBord, dest, from);
+ }
+ else
+ {
+ bord = ReFormeBezierTo (bord, curBord, dest, from);
+ }
+ }
+ else if (nType == descr_interm_bezier)
+ {
+ bord = ReFormeBezierTo (bord, curBord, dest, from);
+ }
+ else
+ {
+ // devrait pas arriver non plus
+ dest->LineTo (getPoint(getEdge(bord).en).x);
+ bord = swdData[bord].suivParc;
+ }
+ // Hard to say what this block of code does really
+ // ForcedPoint gets added in dest but then while dumping the SVG d attribute it has no use so never appears
+ // You can see the code for PathDescrForced in path-description.h to confirm this
+ // Also, I'm not sure if there is anything that increases the degree so oldDegree and totalDegree() would differ
+ // at all.
+ if (bord >= 0 && getPoint(getEdge(bord).st).totalDegree() > 2 ) {
+ dest->ForcePoint ();
+ } else if ( bord >= 0 && getPoint(getEdge(bord).st).oldDegree > 2 && getPoint(getEdge(bord).st).totalDegree() == 2) {
+ if ( splitWhenForced ) {
+ // pour les coupures
+ dest->ForcePoint ();
+ } else {
+ if ( _has_back_data ) {
+ int prevEdge=getPoint(getEdge(bord).st).incidentEdge[FIRST];
+ int nextEdge=getPoint(getEdge(bord).st).incidentEdge[LAST];
+ if ( getEdge(prevEdge).en != getEdge(bord).st ) {
+ int swai=prevEdge;prevEdge=nextEdge;nextEdge=swai;
+ }
+ if ( ebData[prevEdge].pieceID == ebData[nextEdge].pieceID && ebData[prevEdge].pathID == ebData[nextEdge].pathID ) {
+ if ( fabs(ebData[prevEdge].tEn-ebData[nextEdge].tSt) < 0.05 ) {
+ } else {
+ dest->ForcePoint ();
+ }
+ } else {
+ dest->ForcePoint ();
+ }
+ } else {
+ dest->ForcePoint ();
+ }
+ }
+ }
+ }
+ }
+ }
+ dest->Close ();
+}
+
+int
+Shape::ReFormeLineTo (int bord, int /*curBord*/, Path * dest, Path * /*orig*/)
+{
+ int nPiece = ebData[bord].pieceID;
+ int nPath = ebData[bord].pathID;
+ double /*ts=ebData[bord].tSt, */ te = ebData[bord].tEn;
+ Geom::Point nx = getPoint(getEdge(bord).en).x;
+ bord = swdData[bord].suivParc;
+ while (bord >= 0)
+ {
+ if (getPoint(getEdge(bord).st).totalDegree() > 2
+ || getPoint(getEdge(bord).st).oldDegree > 2)
+ {
+ break;
+ }
+ if (ebData[bord].pieceID == nPiece && ebData[bord].pathID == nPath)
+ {
+ if (fabs (te - ebData[bord].tSt) > 0.0001)
+ break;
+ nx = getPoint(getEdge(bord).en).x;
+ te = ebData[bord].tEn;
+ }
+ else
+ {
+ break;
+ }
+ bord = swdData[bord].suivParc;
+ }
+ {
+ dest->LineTo (nx);
+ }
+ return bord;
+}
+
+int
+Shape::ReFormeArcTo (int bord, int /*curBord*/, Path * dest, Path * from)
+{
+ int nPiece = ebData[bord].pieceID;
+ int nPath = ebData[bord].pathID;
+ double ts = ebData[bord].tSt, te = ebData[bord].tEn;
+ // double px=pts[getEdge(bord).st].x,py=pts[getEdge(bord).st].y;
+ Geom::Point nx = getPoint(getEdge(bord).en).x;
+ bord = swdData[bord].suivParc;
+ while (bord >= 0)
+ {
+ if (getPoint(getEdge(bord).st).totalDegree() > 2
+ || getPoint(getEdge(bord).st).oldDegree > 2)
+ {
+ break;
+ }
+ if (ebData[bord].pieceID == nPiece && ebData[bord].pathID == nPath)
+ {
+ if (fabs (te - ebData[bord].tSt) > 0.0001)
+ {
+ break;
+ }
+ nx = getPoint(getEdge(bord).en).x;
+ te = ebData[bord].tEn;
+ }
+ else
+ {
+ break;
+ }
+ bord = swdData[bord].suivParc;
+ }
+ double sang, eang;
+ PathDescrArcTo* nData = dynamic_cast<PathDescrArcTo *>(from->descr_cmd[nPiece]);
+ bool nLarge = nData->large;
+ bool nClockwise = nData->clockwise;
+ Path::ArcAngles (from->PrevPoint (nPiece - 1), nData->p,nData->rx,nData->ry,nData->angle*M_PI/180.0, nLarge, nClockwise, sang, eang);
+ if (nClockwise)
+ {
+ if (sang < eang)
+ sang += 2 * M_PI;
+ }
+ else
+ {
+ if (sang > eang)
+ sang -= 2 * M_PI;
+ }
+ double delta = eang - sang;
+ double ndelta = delta * (te - ts);
+ if (ts > te)
+ nClockwise = !nClockwise;
+ if (ndelta < 0)
+ ndelta = -ndelta;
+ if (ndelta > M_PI)
+ nLarge = true;
+ else
+ nLarge = false;
+ /* if ( delta < 0 ) delta=-delta;
+ if ( ndelta < 0 ) ndelta=-ndelta;
+ if ( ( delta < M_PI && ndelta < M_PI ) || ( delta >= M_PI && ndelta >= M_PI ) ) {
+ if ( ts < te ) {
+ } else {
+ nClockwise=!(nClockwise);
+ }
+ } else {
+ // nLarge=!(nLarge);
+ nLarge=false; // c'est un sous-segment -> l'arc ne peut que etre plus petit
+ if ( ts < te ) {
+ } else {
+ nClockwise=!(nClockwise);
+ }
+ }*/
+ {
+ PathDescrArcTo *nData = dynamic_cast<PathDescrArcTo *>(from->descr_cmd[nPiece]);
+ dest->ArcTo (nx, nData->rx,nData->ry,nData->angle, nLarge, nClockwise);
+ }
+ return bord;
+}
+
+int
+Shape::ReFormeCubicTo (int bord, int /*curBord*/, Path * dest, Path * from)
+{
+ int nPiece = ebData[bord].pieceID;
+ int nPath = ebData[bord].pathID;
+ double ts = ebData[bord].tSt, te = ebData[bord].tEn;
+ Geom::Point nx = getPoint(getEdge(bord).en).x;
+ bord = swdData[bord].suivParc;
+ while (bord >= 0)
+ {
+ if (getPoint(getEdge(bord).st).totalDegree() > 2
+ || getPoint(getEdge(bord).st).oldDegree > 2)
+ {
+ break;
+ }
+ if (ebData[bord].pieceID == nPiece && ebData[bord].pathID == nPath)
+ {
+ if (fabs (te - ebData[bord].tSt) > 0.0001)
+ {
+ break;
+ }
+ nx = getPoint(getEdge(bord).en).x;
+ te = ebData[bord].tEn;
+ }
+ else
+ {
+ break;
+ }
+ bord = swdData[bord].suivParc;
+ }
+ Geom::Point prevx = from->PrevPoint (nPiece - 1);
+
+ Geom::Point sDx, eDx;
+ {
+ PathDescrCubicTo *nData = dynamic_cast<PathDescrCubicTo *>(from->descr_cmd[nPiece]);
+ Path::CubicTangent (ts, sDx, prevx,nData->start,nData->p,nData->end);
+ Path::CubicTangent (te, eDx, prevx,nData->start,nData->p,nData->end);
+ }
+ sDx *= (te - ts);
+ eDx *= (te - ts);
+ {
+ dest->CubicTo (nx,sDx,eDx);
+ }
+ return bord;
+}
+
+int
+Shape::ReFormeBezierTo (int bord, int /*curBord*/, Path * dest, Path * from)
+{
+ int nPiece = ebData[bord].pieceID;
+ int nPath = ebData[bord].pathID;
+ double ts = ebData[bord].tSt, te = ebData[bord].tEn;
+ int ps = nPiece, pe = nPiece;
+ Geom::Point px = getPoint(getEdge(bord).st).x;
+ Geom::Point nx = getPoint(getEdge(bord).en).x;
+ int inBezier = -1, nbInterm = -1;
+ int typ;
+ typ = from->descr_cmd[nPiece]->getType();
+ PathDescrBezierTo *nBData = nullptr;
+ if (typ == descr_bezierto)
+ {
+ nBData = dynamic_cast<PathDescrBezierTo *>(from->descr_cmd[nPiece]);
+ inBezier = nPiece;
+ nbInterm = nBData->nb;
+ }
+ else
+ {
+ int n = nPiece - 1;
+ while (n > 0)
+ {
+ typ = from->descr_cmd[n]->getType();
+ if (typ == descr_bezierto)
+ {
+ inBezier = n;
+ nBData = dynamic_cast<PathDescrBezierTo *>(from->descr_cmd[n]);
+ nbInterm = nBData->nb;
+ break;
+ }
+ n--;
+ }
+ if (inBezier < 0)
+ {
+ bord = swdData[bord].suivParc;
+ dest->LineTo (nx);
+ return bord;
+ }
+ }
+ bord = swdData[bord].suivParc;
+ while (bord >= 0)
+ {
+ if (getPoint(getEdge(bord).st).totalDegree() > 2
+ || getPoint(getEdge(bord).st).oldDegree > 2)
+ {
+ break;
+ }
+ if (ebData[bord].pathID == nPath)
+ {
+ if (ebData[bord].pieceID < inBezier
+ || ebData[bord].pieceID >= inBezier + nbInterm)
+ break;
+ if (ebData[bord].pieceID == pe
+ && fabs (te - ebData[bord].tSt) > 0.0001)
+ break;
+ if (ebData[bord].pieceID != pe
+ && (ebData[bord].tSt > 0.0001 && ebData[bord].tSt < 0.9999))
+ break;
+ if (ebData[bord].pieceID != pe && (te > 0.0001 && te < 0.9999))
+ break;
+ nx = getPoint(getEdge(bord).en).x;
+ te = ebData[bord].tEn;
+ pe = ebData[bord].pieceID;
+ }
+ else
+ {
+ break;
+ }
+ bord = swdData[bord].suivParc;
+ }
+
+ g_return_val_if_fail(nBData != nullptr, 0);
+
+ if (pe == ps)
+ {
+ ReFormeBezierChunk (px, nx, dest, inBezier, nbInterm, from, ps,
+ ts, te);
+ }
+ else if (ps < pe)
+ {
+ if (ts < 0.0001)
+ {
+ if (te > 0.9999)
+ {
+ dest->BezierTo (nx);
+ for (int i = ps; i <= pe; i++)
+ {
+ PathDescrIntermBezierTo *nData = dynamic_cast<PathDescrIntermBezierTo*>(from->descr_cmd[i+1]);
+ dest->IntermBezierTo (nData->p);
+ }
+ dest->EndBezierTo ();
+ }
+ else
+ {
+ Geom::Point tx;
+ {
+ PathDescrIntermBezierTo* psData = dynamic_cast<PathDescrIntermBezierTo*>(from->descr_cmd[pe]);
+ PathDescrIntermBezierTo* pnData = dynamic_cast<PathDescrIntermBezierTo*>(from->descr_cmd[pe+1]);
+ tx = (pnData->p + psData->p) / 2;
+ }
+ dest->BezierTo (tx);
+ for (int i = ps; i < pe; i++)
+ {
+ PathDescrIntermBezierTo* nData = dynamic_cast<PathDescrIntermBezierTo*>(from->descr_cmd[i+1]);
+ dest->IntermBezierTo (nData->p);
+ }
+ dest->EndBezierTo ();
+ ReFormeBezierChunk (tx, nx, dest, inBezier, nbInterm,
+ from, pe, 0.0, te);
+ }
+ }
+ else
+ {
+ if (te > 0.9999)
+ {
+ Geom::Point tx;
+ {
+ PathDescrIntermBezierTo* psData = dynamic_cast<PathDescrIntermBezierTo*>(from->descr_cmd[ps+1]);
+ PathDescrIntermBezierTo* pnData = dynamic_cast<PathDescrIntermBezierTo*>(from->descr_cmd[ps+2]);
+ tx = (psData->p + pnData->p) / 2;
+ }
+ ReFormeBezierChunk (px, tx, dest, inBezier, nbInterm,
+ from, ps, ts, 1.0);
+ dest->BezierTo (nx);
+ for (int i = ps + 1; i <= pe; i++)
+ {
+ PathDescrIntermBezierTo *nData = dynamic_cast<PathDescrIntermBezierTo*>(from->descr_cmd[i+1]);
+ dest->IntermBezierTo (nData->p);
+ }
+ dest->EndBezierTo ();
+ }
+ else
+ {
+ Geom::Point tx;
+ {
+ PathDescrIntermBezierTo* psData = dynamic_cast<PathDescrIntermBezierTo*>(from->descr_cmd[ps+1]);
+ PathDescrIntermBezierTo* pnData = dynamic_cast<PathDescrIntermBezierTo*>(from->descr_cmd[ps+2]);
+ tx = (pnData->p + psData->p) / 2;
+ }
+ ReFormeBezierChunk (px, tx, dest, inBezier, nbInterm,
+ from, ps, ts, 1.0);
+ {
+ PathDescrIntermBezierTo* psData = dynamic_cast<PathDescrIntermBezierTo*>(from->descr_cmd[pe]);
+ PathDescrIntermBezierTo* pnData = dynamic_cast<PathDescrIntermBezierTo*>(from->descr_cmd[pe+1]);
+ tx = (pnData->p + psData->p) / 2;
+ }
+ dest->BezierTo (tx);
+ for (int i = ps + 1; i <= pe; i++)
+ {
+ PathDescrIntermBezierTo* nData = dynamic_cast<PathDescrIntermBezierTo*>(from->descr_cmd[i+1]);
+ dest->IntermBezierTo (nData->p);
+ }
+ dest->EndBezierTo ();
+ ReFormeBezierChunk (tx, nx, dest, inBezier, nbInterm,
+ from, pe, 0.0, te);
+ }
+ }
+ }
+ else
+ {
+ if (ts > 0.9999)
+ {
+ if (te < 0.0001)
+ {
+ dest->BezierTo (nx);
+ for (int i = ps; i >= pe; i--)
+ {
+ PathDescrIntermBezierTo* nData = dynamic_cast<PathDescrIntermBezierTo*>(from->descr_cmd[i+1]);
+ dest->IntermBezierTo (nData->p);
+ }
+ dest->EndBezierTo ();
+ }
+ else
+ {
+ Geom::Point tx;
+ {
+ PathDescrIntermBezierTo* psData = dynamic_cast<PathDescrIntermBezierTo*>(from->descr_cmd[pe+1]);
+ PathDescrIntermBezierTo* pnData = dynamic_cast<PathDescrIntermBezierTo*>(from->descr_cmd[pe+2]);
+ tx = (pnData->p + psData->p) / 2;
+ }
+ dest->BezierTo (tx);
+ for (int i = ps; i > pe; i--)
+ {
+ PathDescrIntermBezierTo* nData = dynamic_cast<PathDescrIntermBezierTo*>(from->descr_cmd[i+1]);
+ dest->IntermBezierTo (nData->p);
+ }
+ dest->EndBezierTo ();
+ ReFormeBezierChunk (tx, nx, dest, inBezier, nbInterm,
+ from, pe, 1.0, te);
+ }
+ }
+ else
+ {
+ if (te < 0.0001)
+ {
+ Geom::Point tx;
+ {
+ PathDescrIntermBezierTo* psData = dynamic_cast<PathDescrIntermBezierTo*>(from->descr_cmd[ps]);
+ PathDescrIntermBezierTo* pnData = dynamic_cast<PathDescrIntermBezierTo*>(from->descr_cmd[ps+1]);
+ tx = (pnData->p + psData->p) / 2;
+ }
+ ReFormeBezierChunk (px, tx, dest, inBezier, nbInterm,
+ from, ps, ts, 0.0);
+ dest->BezierTo (nx);
+ for (int i = ps + 1; i >= pe; i--)
+ {
+ PathDescrIntermBezierTo* nData = dynamic_cast<PathDescrIntermBezierTo*>(from->descr_cmd[i]);
+ dest->IntermBezierTo (nData->p);
+ }
+ dest->EndBezierTo ();
+ }
+ else
+ {
+ Geom::Point tx;
+ {
+ PathDescrIntermBezierTo* psData = dynamic_cast<PathDescrIntermBezierTo*>(from->descr_cmd[ps]);
+ PathDescrIntermBezierTo* pnData = dynamic_cast<PathDescrIntermBezierTo*>(from->descr_cmd[ps+1]);
+ tx = (pnData->p + psData->p) / 2;
+ }
+ ReFormeBezierChunk (px, tx, dest, inBezier, nbInterm,
+ from, ps, ts, 0.0);
+ {
+ PathDescrIntermBezierTo* psData = dynamic_cast<PathDescrIntermBezierTo*>(from->descr_cmd[pe+1]);
+ PathDescrIntermBezierTo* pnData = dynamic_cast<PathDescrIntermBezierTo*>(from->descr_cmd[pe+2]);
+ tx = (pnData->p + psData->p) / 2;
+ }
+ dest->BezierTo (tx);
+ for (int i = ps + 1; i > pe; i--)
+ {
+ PathDescrIntermBezierTo* nData = dynamic_cast<PathDescrIntermBezierTo*>(from->descr_cmd[i]);
+ dest->IntermBezierTo (nData->p);
+ }
+ dest->EndBezierTo ();
+ ReFormeBezierChunk (tx, nx, dest, inBezier, nbInterm,
+ from, pe, 1.0, te);
+ }
+ }
+ }
+ return bord;
+}
+
+void
+Shape::ReFormeBezierChunk (Geom::Point px, Geom::Point nx,
+ Path * dest, int inBezier, int nbInterm,
+ Path * from, int p, double ts, double te)
+{
+ PathDescrBezierTo* nBData = dynamic_cast<PathDescrBezierTo*>(from->descr_cmd[inBezier]);
+ Geom::Point bstx = from->PrevPoint (inBezier - 1);
+ Geom::Point benx = nBData->p;
+
+ Geom::Point mx;
+ if (p == inBezier)
+ {
+ // premier bout
+ if (nbInterm <= 1)
+ {
+ // seul bout de la spline
+ PathDescrIntermBezierTo *nData = dynamic_cast<PathDescrIntermBezierTo*>(from->descr_cmd[inBezier+1]);
+ mx = nData->p;
+ }
+ else
+ {
+ // premier bout d'une spline qui en contient plusieurs
+ PathDescrIntermBezierTo *nData = dynamic_cast<PathDescrIntermBezierTo*>(from->descr_cmd[inBezier+1]);
+ mx = nData->p;
+ nData = dynamic_cast<PathDescrIntermBezierTo*>(from->descr_cmd[inBezier+2]);
+ benx = (nData->p + mx) / 2;
+ }
+ }
+ else if (p == inBezier + nbInterm - 1)
+ {
+ // dernier bout
+ // si nbInterm == 1, le cas a deja ete traite
+ // donc dernier bout d'une spline qui en contient plusieurs
+ PathDescrIntermBezierTo* nData = dynamic_cast<PathDescrIntermBezierTo*>(from->descr_cmd[inBezier+nbInterm]);
+ mx = nData->p;
+ nData = dynamic_cast<PathDescrIntermBezierTo*>(from->descr_cmd[inBezier+nbInterm-1]);
+ bstx = (nData->p + mx) / 2;
+ }
+ else
+ {
+ // la spline contient forcément plusieurs bouts, et ce n'est ni le premier ni le dernier
+ PathDescrIntermBezierTo *nData = dynamic_cast<PathDescrIntermBezierTo*>(from->descr_cmd[p+1]);
+ mx = nData->p;
+ nData = dynamic_cast<PathDescrIntermBezierTo*>(from->descr_cmd[p]);
+ bstx = (nData->p + mx) / 2;
+ nData = dynamic_cast<PathDescrIntermBezierTo*>(from->descr_cmd[p+2]);
+ benx = (nData->p + mx) / 2;
+ }
+ Geom::Point cx;
+ {
+ Path::QuadraticPoint ((ts + te) / 2, cx, bstx, mx, benx);
+ }
+ cx = 2 * cx - (px + nx) / 2;
+ {
+ dest->BezierTo (nx);
+ dest->IntermBezierTo (cx);
+ dest->EndBezierTo ();
+ }
+}
+
+#undef MiscNormalize
diff --git a/src/livarot/ShapeRaster.cpp b/src/livarot/ShapeRaster.cpp
new file mode 100644
index 0000000..7aa300e
--- /dev/null
+++ b/src/livarot/ShapeRaster.cpp
@@ -0,0 +1,2014 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * TODO: insert short description here
+ *//*
+ * Authors: see git history
+ *
+ * Copyright (C) 2018 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+/*
+ * ShapeRaster.cpp
+ * nlivarot
+ *
+ * Created by fred on Sat Jul 19 2003.
+ *
+ */
+
+#include "Shape.h"
+
+#include "livarot/float-line.h"
+#include "AlphaLigne.h"
+#include "BitLigne.h"
+
+#include "livarot/sweep-event-queue.h"
+#include "livarot/sweep-tree-list.h"
+#include "livarot/sweep-tree.h"
+
+/*
+ * polygon rasterization: the sweepline algorithm in all its glory
+ * nothing unusual in this implementation, so nothing special to say
+ * the *Quick*() functions are not useful. forget about them
+ */
+
+void Shape::BeginRaster(float &pos, int &curPt)
+{
+ if ( numberOfPoints() <= 1 || numberOfEdges() <= 1 ) {
+ curPt = 0;
+ pos = 0;
+ return;
+ }
+
+ MakeRasterData(true);
+ MakePointData(true);
+ MakeEdgeData(true);
+
+ if (sTree == nullptr) {
+ sTree = new SweepTreeList(numberOfEdges());
+ }
+ if (sEvts == nullptr) {
+ sEvts = new SweepEventQueue(numberOfEdges());
+ }
+
+ SortPoints();
+
+ curPt = 0;
+ pos = getPoint(0).x[1] - 1.0;
+
+ for (int i = 0; i < numberOfPoints(); i++) {
+ pData[i].pending = 0;
+ pData[i].edgeOnLeft = -1;
+ pData[i].nextLinkedPoint = -1;
+ pData[i].rx[0] = /*Round(*/getPoint(i).x[0]/*)*/;
+ pData[i].rx[1] = /*Round(*/getPoint(i).x[1]/*)*/;
+ }
+
+ for (int i = 0;i < numberOfEdges(); i++) {
+ swrData[i].misc = nullptr;
+ eData[i].rdx=pData[getEdge(i).en].rx - pData[getEdge(i).st].rx;
+ }
+}
+
+
+void Shape::EndRaster()
+{
+ delete sTree;
+ sTree = nullptr;
+ delete sEvts;
+ sEvts = nullptr;
+
+ MakePointData(false);
+ MakeEdgeData(false);
+ MakeRasterData(false);
+}
+
+
+void Shape::BeginQuickRaster(float &pos, int &curPt)
+{
+ if ( numberOfPoints() <= 1 || numberOfEdges() <= 1 ) {
+ curPt = 0;
+ pos = 0;
+ return;
+ }
+
+ MakeRasterData(true);
+ MakeQuickRasterData(true);
+ nbQRas = 0;
+ firstQRas = lastQRas = -1;
+ MakePointData(true);
+ MakeEdgeData(true);
+
+ curPt = 0;
+ pos = getPoint(0).x[1] - 1.0;
+
+ initialisePointData();
+
+ for (int i=0;i<numberOfEdges();i++) {
+ swrData[i].misc = nullptr;
+ qrsData[i].ind = -1;
+ eData[i].rdx = pData[getEdge(i).en].rx - pData[getEdge(i).st].rx;
+ }
+
+ SortPoints();
+// SortPointsRounded();
+}
+
+
+void Shape::EndQuickRaster()
+{
+ MakePointData(false);
+ MakeEdgeData(false);
+ MakeRasterData(false);
+ MakeQuickRasterData(false);
+}
+
+
+// 2 versions of the Scan() series to move the scanline to a given position withou actually computing coverages
+void Shape::Scan(float &pos, int &curP, float to, float step)
+{
+ if ( numberOfEdges() <= 1 ) {
+ return;
+ }
+
+ if ( pos == to ) {
+ return;
+ }
+
+ enum Direction {
+ DOWNWARDS,
+ UPWARDS
+ };
+
+ Direction const d = (pos < to) ? DOWNWARDS : UPWARDS;
+
+ // points of the polygon are sorted top-down, so we take them in order, starting with the one at index curP,
+ // until we reach the wanted position to.
+ // don't forget to update curP and pos when we're done
+ int curPt = curP;
+ while ( ( d == DOWNWARDS && curPt < numberOfPoints() && getPoint(curPt).x[1] <= to) ||
+ ( d == UPWARDS && curPt > 0 && getPoint(curPt - 1).x[1] >= to) )
+ {
+ int nPt = (d == DOWNWARDS) ? curPt++ : --curPt;
+
+ // treat a new point: remove and add edges incident to it
+ int nbUp;
+ int nbDn;
+ int upNo;
+ int dnNo;
+ _countUpDown(nPt, &nbUp, &nbDn, &upNo, &dnNo);
+
+ if ( d == DOWNWARDS ) {
+ if ( nbDn <= 0 ) {
+ upNo = -1;
+ }
+ if ( upNo >= 0 && swrData[upNo].misc == nullptr ) {
+ upNo = -1;
+ }
+ } else {
+ if ( nbUp <= 0 ) {
+ dnNo = -1;
+ }
+ if ( dnNo >= 0 && swrData[dnNo].misc == nullptr ) {
+ dnNo = -1;
+ }
+ }
+
+ if ( ( d == DOWNWARDS && nbUp > 0 ) || ( d == UPWARDS && nbDn > 0 ) ) {
+ // first remove edges coming from above or below, as appropriate
+ int cb = getPoint(nPt).incidentEdge[FIRST];
+ while ( cb >= 0 && cb < numberOfEdges() ) {
+
+ Shape::dg_arete const &e = getEdge(cb);
+ if ( (d == DOWNWARDS && nPt == std::max(e.st, e.en)) ||
+ (d == UPWARDS && nPt == std::min(e.st, e.en)) )
+ {
+ if ( ( d == DOWNWARDS && cb != upNo ) || ( d == UPWARDS && cb != dnNo ) ) {
+ // we salvage the edge upNo to plug the edges we'll be addingat its place
+ // but the other edge don't have this chance
+ SweepTree *node = swrData[cb].misc;
+ if ( node ) {
+ swrData[cb].misc = nullptr;
+ node->Remove(*sTree, *sEvts, true);
+ }
+ }
+ }
+ cb = NextAt(nPt, cb);
+ }
+ }
+
+ // if there is one edge going down and one edge coming from above, we don't Insert() the new edge,
+ // but replace the upNo edge by the new one (faster)
+ SweepTree* insertionNode = nullptr;
+ if ( dnNo >= 0 ) {
+ if ( upNo >= 0 ) {
+ int rmNo=(d == DOWNWARDS) ? upNo:dnNo;
+ int neNo=(d == DOWNWARDS) ? dnNo:upNo;
+ SweepTree* node = swrData[rmNo].misc;
+ swrData[rmNo].misc = nullptr;
+
+ int const P = (d == DOWNWARDS) ? nPt : Other(nPt, neNo);
+ node->ConvertTo(this, neNo, 1, P);
+
+ swrData[neNo].misc = node;
+ insertionNode = node;
+ CreateEdge(neNo, to, step);
+ } else {
+ // always DOWNWARDS
+ SweepTree* node = sTree->add(this, dnNo, 1, nPt, this);
+ swrData[dnNo].misc = node;
+ node->Insert(*sTree, *sEvts, this, nPt, true);
+ //if (d == UPWARDS) {
+ // node->startPoint = Other(nPt, dnNo);
+ //}
+ insertionNode = node;
+ CreateEdge(dnNo,to,step);
+ }
+ } else {
+ if ( upNo >= 0 ) {
+ // always UPWARDS
+ SweepTree* node = sTree->add(this, upNo, 1, nPt, this);
+ swrData[upNo].misc = node;
+ node->Insert(*sTree, *sEvts, this, nPt, true);
+ //if (d == UPWARDS) {
+ node->startPoint = Other(nPt, upNo);
+ //}
+ insertionNode = node;
+ CreateEdge(upNo,to,step);
+ }
+ }
+
+ // add the remaining edges
+ if ( ( d == DOWNWARDS && nbDn > 1 ) || ( d == UPWARDS && nbUp > 1 ) ) {
+ // si nbDn == 1 , alors dnNo a deja ete traite
+ int cb = getPoint(nPt).incidentEdge[FIRST];
+ while ( cb >= 0 && cb < numberOfEdges() ) {
+ Shape::dg_arete const &e = getEdge(cb);
+ if ( nPt == std::min(e.st, e.en) ) {
+ if ( cb != dnNo && cb != upNo ) {
+ SweepTree *node = sTree->add(this, cb, 1, nPt, this);
+ swrData[cb].misc = node;
+ node->InsertAt(*sTree, *sEvts, this, insertionNode, nPt, true);
+ if (d == UPWARDS) {
+ node->startPoint = Other(nPt, cb);
+ }
+ CreateEdge(cb, to, step);
+ }
+ }
+ cb = NextAt(nPt,cb);
+ }
+ }
+ }
+
+ curP = curPt;
+ if ( curPt > 0 ) {
+ pos = getPoint(curPt - 1).x[1];
+ } else {
+ pos = to;
+ }
+
+ // the final touch: edges intersecting the sweepline must be update so that their intersection with
+ // said sweepline is correct.
+ pos = to;
+ if ( sTree->racine ) {
+ SweepTree* curS = static_cast<SweepTree*>(sTree->racine->Leftmost());
+ while ( curS ) {
+ int cb = curS->bord;
+ AvanceEdge(cb, to, true, step);
+ curS = static_cast<SweepTree*>(curS->elem[RIGHT]);
+ }
+ }
+}
+
+
+
+void Shape::QuickScan(float &pos,int &curP, float to, bool /*doSort*/, float step)
+{
+ if ( numberOfEdges() <= 1 ) {
+ return;
+ }
+
+ if ( pos == to ) {
+ return;
+ }
+
+ enum Direction {
+ DOWNWARDS,
+ UPWARDS
+ };
+
+ Direction const d = (pos < to) ? DOWNWARDS : UPWARDS;
+
+ int curPt = curP;
+ while ( (d == DOWNWARDS && curPt < numberOfPoints() && getPoint(curPt ).x[1] <= to) ||
+ (d == UPWARDS && curPt > 0 && getPoint(curPt - 1).x[1] >= to) )
+ {
+ int nPt = (d == DOWNWARDS) ? curPt++ : --curPt;
+
+ int nbUp;
+ int nbDn;
+ int upNo;
+ int dnNo;
+ _countUpDown(nPt, &nbUp, &nbDn, &upNo, &dnNo);
+
+ if ( nbDn <= 0 ) {
+ upNo = -1;
+ }
+ if ( upNo >= 0 && swrData[upNo].misc == nullptr ) {
+ upNo = -1;
+ }
+
+ if ( nbUp > 0 ) {
+ int cb = getPoint(nPt).incidentEdge[FIRST];
+ while ( cb >= 0 && cb < numberOfEdges() ) {
+ Shape::dg_arete const &e = getEdge(cb);
+ if ( (d == DOWNWARDS && nPt == std::max(e.st, e.en)) ||
+ (d == UPWARDS && nPt == std::min(e.st, e.en)) )
+ {
+ if ( cb != upNo ) {
+ QuickRasterSubEdge(cb);
+ }
+ }
+ cb = NextAt(nPt,cb);
+ }
+ }
+
+ // traitement du "upNo devient dnNo"
+ int ins_guess = -1;
+ if ( dnNo >= 0 ) {
+ if ( upNo >= 0 ) {
+ ins_guess = QuickRasterChgEdge(upNo, dnNo, getPoint(nPt).x[0]);
+ } else {
+ ins_guess = QuickRasterAddEdge(dnNo, getPoint(nPt).x[0], ins_guess);
+ }
+ CreateEdge(dnNo, to, step);
+ }
+
+ if ( nbDn > 1 ) { // si nbDn == 1 , alors dnNo a deja ete traite
+ int cb = getPoint(nPt).incidentEdge[FIRST];
+ while ( cb >= 0 && cb < numberOfEdges() ) {
+ Shape::dg_arete const &e = getEdge(cb);
+ if ( (d == DOWNWARDS && nPt == std::min(e.st, e.en)) ||
+ (d == UPWARDS && nPt == std::max(e.st, e.en)) )
+ {
+ if ( cb != dnNo ) {
+ ins_guess = QuickRasterAddEdge(cb, getPoint(nPt).x[0], ins_guess);
+ CreateEdge(cb, to, step);
+ }
+ }
+ cb = NextAt(nPt,cb);
+ }
+ }
+
+
+ curP = curPt;
+ if ( curPt > 0 ) {
+ pos = getPoint(curPt-1).x[1];
+ } else {
+ pos = to;
+ }
+ }
+
+ pos = to;
+
+ for (int i=0; i < nbQRas; i++) {
+ int cb = qrsData[i].bord;
+ AvanceEdge(cb, to, true, step);
+ qrsData[i].x=swrData[cb].curX;
+ }
+
+ QuickRasterSort();
+}
+
+
+
+int Shape::QuickRasterChgEdge(int oBord, int nBord, double x)
+{
+ if ( oBord == nBord ) {
+ return -1;
+ }
+
+ int no = qrsData[oBord].ind;
+ if ( no >= 0 ) {
+ qrsData[no].bord = nBord;
+ qrsData[no].x = x;
+ qrsData[oBord].ind = -1;
+ qrsData[nBord].ind = no;
+ }
+
+ return no;
+}
+
+
+
+int Shape::QuickRasterAddEdge(int bord, double x, int guess)
+{
+ int no = nbQRas++;
+ qrsData[no].bord = bord;
+ qrsData[no].x = x;
+ qrsData[bord].ind = no;
+ qrsData[no].prev = -1;
+ qrsData[no].next = -1;
+
+ if ( no < 0 || no >= nbQRas ) {
+ return -1;
+ }
+
+ if ( firstQRas < 0 ) {
+ firstQRas = lastQRas = no;
+ qrsData[no].prev = -1;
+ qrsData[no].next = -1;
+ return no;
+ }
+
+ if ( guess < 0 || guess >= nbQRas ) {
+
+ int c = firstQRas;
+ while ( c >= 0 && c < nbQRas && CmpQRs(qrsData[c],qrsData[no]) < 0 ) {
+ c = qrsData[c].next;
+ }
+
+ if ( c < 0 || c >= nbQRas ) {
+ qrsData[no].prev = lastQRas;
+ qrsData[lastQRas].next = no;
+ lastQRas = no;
+ } else {
+ qrsData[no].prev = qrsData[c].prev;
+ if ( qrsData[no].prev >= 0 ) {
+ qrsData[qrsData[no].prev].next=no;
+ } else {
+ firstQRas = no;
+ }
+
+ qrsData[no].next = c;
+ qrsData[c].prev = no;
+ }
+
+ } else {
+ int c = guess;
+ int stTst = CmpQRs(qrsData[c],qrsData[no]);
+ if ( stTst == 0 ) {
+
+ qrsData[no].prev = qrsData[c].prev;
+ if ( qrsData[no].prev >= 0 ) {
+ qrsData[qrsData[no].prev].next = no;
+ } else {
+ firstQRas = no;
+ }
+
+ qrsData[no].next = c;
+ qrsData[c].prev = no;
+
+ } else if ( stTst > 0 ) {
+
+ while ( c >= 0 && c < nbQRas && CmpQRs(qrsData[c],qrsData[no]) > 0 ) {
+ c = qrsData[c].prev;
+ }
+
+ if ( c < 0 || c >= nbQRas ) {
+ qrsData[no].next = firstQRas;
+ qrsData[firstQRas].prev = no; // firstQRas != -1
+ firstQRas = no;
+ } else {
+ qrsData[no].next = qrsData[c].next;
+ if ( qrsData[no].next >= 0 ) {
+ qrsData[qrsData[no].next].prev = no;
+ } else {
+ lastQRas = no;
+ }
+ qrsData[no].prev = c;
+ qrsData[c].next = no;
+ }
+
+ } else {
+
+ while ( c >= 0 && c < nbQRas && CmpQRs(qrsData[c],qrsData[no]) < 0 ) {
+ c = qrsData[c].next;
+ }
+
+ if ( c < 0 || c >= nbQRas ) {
+ qrsData[no].prev = lastQRas;
+ qrsData[lastQRas].next = no;
+ lastQRas = no;
+ } else {
+ qrsData[no].prev = qrsData[c].prev;
+ if ( qrsData[no].prev >= 0 ) {
+ qrsData[qrsData[no].prev].next = no;
+ } else {
+ firstQRas = no;
+ }
+
+ qrsData[no].next = c;
+ qrsData[c].prev = no;
+ }
+ }
+ }
+
+ return no;
+}
+
+
+
+void Shape::QuickRasterSubEdge(int bord)
+{
+ int no = qrsData[bord].ind;
+ if ( no < 0 || no >= nbQRas ) {
+ return; // euuhHHH
+ }
+
+ if ( qrsData[no].prev >= 0 ) {
+ qrsData[qrsData[no].prev].next=qrsData[no].next;
+ }
+
+ if ( qrsData[no].next >= 0 ) {
+ qrsData[qrsData[no].next].prev = qrsData[no].prev;
+ }
+
+ if ( no == firstQRas ) {
+ firstQRas = qrsData[no].next;
+ }
+
+ if ( no == lastQRas ) {
+ lastQRas = qrsData[no].prev;
+ }
+
+ qrsData[no].prev = qrsData[no].next = -1;
+
+ int savInd = qrsData[no].ind;
+ qrsData[no] = qrsData[--nbQRas];
+ qrsData[no].ind = savInd;
+ qrsData[qrsData[no].bord].ind = no;
+ qrsData[bord].ind = -1;
+
+ if ( nbQRas > 0 ) {
+ if ( firstQRas == nbQRas ) {
+ firstQRas = no;
+ }
+ if ( lastQRas == nbQRas ) {
+ lastQRas = no;
+ }
+ if ( qrsData[no].prev >= 0 ) {
+ qrsData[qrsData[no].prev].next = no;
+ }
+ if ( qrsData[no].next >= 0 ) {
+ qrsData[qrsData[no].next].prev = no;
+ }
+ }
+}
+
+
+
+void Shape::QuickRasterSwapEdge(int a, int b)
+{
+ if ( a == b ) {
+ return;
+ }
+
+ int na = qrsData[a].ind;
+ int nb = qrsData[b].ind;
+ if ( na < 0 || na >= nbQRas || nb < 0 || nb >= nbQRas ) {
+ return; // errrm
+ }
+
+ qrsData[na].bord = b;
+ qrsData[nb].bord = a;
+ qrsData[a].ind = nb;
+ qrsData[b].ind = na;
+
+ double swd = qrsData[na].x;
+ qrsData[na].x = qrsData[nb].x;
+ qrsData[nb].x = swd;
+}
+
+
+void Shape::QuickRasterSort()
+{
+ if ( nbQRas <= 1 ) {
+ return;
+ }
+
+ int cb = qrsData[firstQRas].bord;
+
+ while ( cb >= 0 ) {
+ int bI = qrsData[cb].ind;
+ int nI = qrsData[bI].next;
+
+ if ( nI < 0 ) {
+ break;
+ }
+
+ int ncb = qrsData[nI].bord;
+ if ( CmpQRs(qrsData[nI], qrsData[bI]) < 0 ) {
+ QuickRasterSwapEdge(cb, ncb);
+ int pI = qrsData[bI].prev; // ca reste bI, puisqu'on a juste echange les contenus
+ if ( pI < 0 ) {
+ cb = ncb; // en fait inutile; mais bon...
+ } else {
+ int pcb = qrsData[pI].bord;
+ cb = pcb;
+ }
+ } else {
+ cb = ncb;
+ }
+ }
+}
+
+
+// direct scan to a given position. goes through the edge list to keep only the ones intersecting the target sweepline
+// good for initial setup of scanline algo, bad for incremental changes
+void Shape::DirectScan(float &pos, int &curP, float to, float step)
+{
+ if ( numberOfEdges() <= 1 ) {
+ return;
+ }
+
+ if ( pos == to ) {
+ return;
+ }
+
+ if ( pos < to ) {
+ // we're moving downwards
+ // points of the polygon are sorted top-down, so we take them in order, starting with the one at index curP,
+ // until we reach the wanted position to.
+ // don't forget to update curP and pos when we're done
+ int curPt = curP;
+ while ( curPt < numberOfPoints() && getPoint(curPt).x[1] <= to ) {
+ curPt++;
+ }
+
+ for (int i=0;i<numberOfEdges();i++) {
+ if ( swrData[i].misc ) {
+ SweepTree* node = swrData[i].misc;
+ swrData[i].misc = nullptr;
+ node->Remove(*sTree, *sEvts, true);
+ }
+ }
+
+ for (int i=0; i < numberOfEdges(); i++) {
+ Shape::dg_arete const &e = getEdge(i);
+ if ( ( e.st < curPt && e.en >= curPt ) || ( e.en < curPt && e.st >= curPt )) {
+ // crosses sweepline
+ int nPt = (e.st < curPt) ? e.st : e.en;
+ SweepTree* node = sTree->add(this, i, 1, nPt, this);
+ swrData[i].misc = node;
+ node->Insert(*sTree, *sEvts, this, nPt, true);
+ CreateEdge(i, to, step);
+ }
+ }
+
+ curP = curPt;
+ if ( curPt > 0 ) {
+ pos = getPoint(curPt - 1).x[1];
+ } else {
+ pos = to;
+ }
+
+ } else {
+
+ // same thing, but going up. so the sweepSens is inverted for the Find() function
+ int curPt=curP;
+ while ( curPt > 0 && getPoint(curPt-1).x[1] >= to ) {
+ curPt--;
+ }
+
+ for (int i = 0; i < numberOfEdges(); i++) {
+ if ( swrData[i].misc ) {
+ SweepTree* node = swrData[i].misc;
+ swrData[i].misc = nullptr;
+ node->Remove(*sTree, *sEvts, true);
+ }
+ }
+
+ for (int i=0;i<numberOfEdges();i++) {
+ Shape::dg_arete const &e = getEdge(i);
+ if ( ( e.st > curPt - 1 && e.en <= curPt - 1 ) || ( e.en > curPt - 1 && e.st <= curPt - 1 )) {
+ // crosses sweepline
+ int nPt = (e.st > curPt) ? e.st : e.en;
+ SweepTree* node = sTree->add(this, i, 1, nPt, this);
+ swrData[i].misc = node;
+ node->Insert(*sTree, *sEvts, this, nPt, false);
+ node->startPoint = Other(nPt, i);
+ CreateEdge(i, to, step);
+ }
+ }
+
+ curP = curPt;
+ if ( curPt > 0 ) {
+ pos = getPoint(curPt - 1).x[1];
+ } else {
+ pos = to;
+ }
+ }
+
+ // the final touch: edges intersecting the sweepline must be update so that their intersection with
+ // said sweepline is correct.
+ pos = to;
+ if ( sTree->racine ) {
+ SweepTree* curS=static_cast<SweepTree*>(sTree->racine->Leftmost());
+ while ( curS ) {
+ int cb = curS->bord;
+ AvanceEdge(cb, to, true, step);
+ curS = static_cast<SweepTree*>(curS->elem[RIGHT]);
+ }
+ }
+}
+
+
+
+void Shape::DirectQuickScan(float &pos, int &curP, float to, bool /*doSort*/, float step)
+{
+ if ( numberOfEdges() <= 1 ) {
+ return;
+ }
+
+ if ( pos == to ) {
+ return;
+ }
+
+ if ( pos < to ) {
+ // we're moving downwards
+ // points of the polygon are sorted top-down, so we take them in order, starting with the one at index curP,
+ // until we reach the wanted position to.
+ // don't forget to update curP and pos when we're done
+ int curPt=curP;
+ while ( curPt < numberOfPoints() && getPoint(curPt).x[1] <= to ) {
+ curPt++;
+ }
+
+ for (int i = 0; i < numberOfEdges(); i++) {
+ if ( qrsData[i].ind < 0 ) {
+ QuickRasterSubEdge(i);
+ }
+ }
+
+ for (int i = 0; i < numberOfEdges(); i++) {
+ Shape::dg_arete const &e = getEdge(i);
+ if ( ( e.st < curPt && e.en >= curPt ) || ( e.en < curPt && e.st >= curPt )) {
+ // crosses sweepline
+ int nPt = (e.st < e.en) ? e.st : e.en;
+ QuickRasterAddEdge(i, getPoint(nPt).x[0], -1);
+ CreateEdge(i, to, step);
+ }
+ }
+
+ curP = curPt;
+ if ( curPt > 0 ) {
+ pos=getPoint(curPt-1).x[1];
+ } else {
+ pos = to;
+ }
+
+ } else {
+
+ // same thing, but going up. so the sweepSens is inverted for the Find() function
+ int curPt=curP;
+ while ( curPt > 0 && getPoint(curPt-1).x[1] >= to ) {
+ curPt--;
+ }
+
+ for (int i = 0; i < numberOfEdges(); i++) {
+ if ( qrsData[i].ind < 0 ) {
+ QuickRasterSubEdge(i);
+ }
+ }
+
+ for (int i=0;i<numberOfEdges();i++) {
+ Shape::dg_arete const &e = getEdge(i);
+ if ( ( e.st < curPt-1 && e.en >= curPt-1 ) || ( e.en < curPt-1 && e.st >= curPt-1 )) {
+ // crosses sweepline
+ int nPt = (e.st > e.en) ? e.st : e.en;
+ QuickRasterAddEdge(i, getPoint(nPt).x[0], -1);
+ CreateEdge(i, to, step);
+ }
+ }
+
+ curP = curPt;
+ if ( curPt > 0 ) {
+ pos = getPoint(curPt-1).x[1];
+ } else {
+ pos = to;
+ }
+
+ }
+
+ pos = to;
+ for (int i = 0; i < nbQRas; i++) {
+ int cb = qrsData[i].bord;
+ AvanceEdge(cb, to, true, step);
+ qrsData[i].x = swrData[cb].curX;
+ }
+
+ QuickRasterSort();
+}
+
+
+// scan and compute coverage, FloatLigne version coverage of the line is bult in 2 parts: first a
+// set of rectangles of height the height of the line (here: "step") one rectangle for each portion
+// of the sweepline that is in the polygon at the beginning of the scan. then a set ot trapezoids
+// are added or removed to these rectangles, one trapezoid for each edge destroyed or edge crossing
+// the entire line. think of it as a refinement of the coverage by rectangles
+
+void Shape::Scan(float &pos, int &curP, float to, FloatLigne *line, bool exact, float step)
+{
+ if ( numberOfEdges() <= 1 ) {
+ return;
+ }
+
+ if ( pos >= to ) {
+ return;
+ }
+
+ // first step: the rectangles since we read the sweepline left to right, we know the
+ // boundaries of the rectangles are appended in a list, hence the AppendBord(). we salvage
+ // the guess value for the trapezoids the edges will induce
+
+ if ( sTree->racine ) {
+ SweepTree *curS = static_cast<SweepTree*>(sTree->racine->Leftmost());
+ while ( curS ) {
+
+ int lastGuess = -1;
+ int cb = curS->bord;
+
+ if ( swrData[cb].sens == false && curS->elem[LEFT] ) {
+
+ int lb = (static_cast<SweepTree*>(curS->elem[LEFT]))->bord;
+
+ lastGuess = line->AppendBord(swrData[lb].curX,
+ to - swrData[lb].curY,
+ swrData[cb].curX,
+ to - swrData[cb].curY,0.0);
+
+ swrData[lb].guess = lastGuess - 1;
+ swrData[cb].guess = lastGuess;
+ } else {
+ int lb = curS->bord;
+ swrData[lb].guess = -1;
+ }
+
+ curS=static_cast <SweepTree*> (curS->elem[RIGHT]);
+ }
+ }
+
+ int curPt = curP;
+ while ( curPt < numberOfPoints() && getPoint(curPt).x[1] <= to ) {
+
+ int nPt = curPt++;
+
+ // same thing as the usual Scan(), just with a hardcoded "indegree+outdegree=2" case, since
+ // it's the most common one
+
+ int nbUp;
+ int nbDn;
+ int upNo;
+ int dnNo;
+ if ( getPoint(nPt).totalDegree() == 2 ) {
+ _countUpDownTotalDegree2(nPt, &nbUp, &nbDn, &upNo, &dnNo);
+ } else {
+ _countUpDown(nPt, &nbUp, &nbDn, &upNo, &dnNo);
+ }
+
+ if ( nbDn <= 0 ) {
+ upNo = -1;
+ }
+ if ( upNo >= 0 && swrData[upNo].misc == nullptr ) {
+ upNo = -1;
+ }
+
+ if ( nbUp > 1 || ( nbUp == 1 && upNo < 0 ) ) {
+ int cb = getPoint(nPt).incidentEdge[FIRST];
+ while ( cb >= 0 && cb < numberOfEdges() ) {
+ Shape::dg_arete const &e = getEdge(cb);
+ if ( nPt == std::max(e.st, e.en) ) {
+ if ( cb != upNo ) {
+ SweepTree* node = swrData[cb].misc;
+ if ( node ) {
+ _updateIntersection(cb, nPt);
+ // create trapezoid for the chunk of edge intersecting with the line
+ DestroyEdge(cb, to, line);
+ node->Remove(*sTree, *sEvts, true);
+ }
+ }
+ }
+
+ cb = NextAt(nPt,cb);
+ }
+ }
+
+ // traitement du "upNo devient dnNo"
+ SweepTree *insertionNode = nullptr;
+ if ( dnNo >= 0 ) {
+ if ( upNo >= 0 ) {
+ SweepTree* node = swrData[upNo].misc;
+ _updateIntersection(upNo, nPt);
+ DestroyEdge(upNo, to, line);
+
+ node->ConvertTo(this, dnNo, 1, nPt);
+
+ swrData[dnNo].misc = node;
+ insertionNode = node;
+ CreateEdge(dnNo, to, step);
+ swrData[dnNo].guess = swrData[upNo].guess;
+ } else {
+ SweepTree *node = sTree->add(this, dnNo, 1, nPt, this);
+ swrData[dnNo].misc = node;
+ node->Insert(*sTree, *sEvts, this, nPt, true);
+ insertionNode = node;
+ CreateEdge(dnNo, to, step);
+ }
+ }
+
+ if ( nbDn > 1 ) { // si nbDn == 1 , alors dnNo a deja ete traite
+ int cb = getPoint(nPt).incidentEdge[FIRST];
+ while ( cb >= 0 && cb < numberOfEdges() ) {
+ Shape::dg_arete const &e = getEdge(cb);
+ if ( nPt == std::min(e.st, e.en) ) {
+ if ( cb != dnNo ) {
+ SweepTree *node = sTree->add(this, cb, 1, nPt, this);
+ swrData[cb].misc = node;
+ node->InsertAt(*sTree, *sEvts, this, insertionNode, nPt, true);
+ CreateEdge(cb, to, step);
+ }
+ }
+ cb = NextAt(nPt,cb);
+ }
+ }
+ }
+
+ curP = curPt;
+ if ( curPt > 0 ) {
+ pos = getPoint(curPt - 1).x[1];
+ } else {
+ pos = to;
+ }
+
+ // update intersections with the sweepline, and add trapezoids for edges crossing the line
+ pos = to;
+ if ( sTree->racine ) {
+ SweepTree* curS = static_cast<SweepTree*>(sTree->racine->Leftmost());
+ while ( curS ) {
+ int cb = curS->bord;
+ AvanceEdge(cb, to, line, exact, step);
+ curS = static_cast<SweepTree*>(curS->elem[RIGHT]);
+ }
+ }
+}
+
+
+
+
+void Shape::Scan(float &pos, int &curP, float to, FillRule directed, BitLigne *line, bool exact, float step)
+{
+ if ( numberOfEdges() <= 1 ) {
+ return;
+ }
+
+ if ( pos >= to ) {
+ return;
+ }
+
+ if ( sTree->racine ) {
+ int curW = 0;
+ float lastX = 0;
+ SweepTree* curS = static_cast<SweepTree*>(sTree->racine->Leftmost());
+
+ if ( directed == fill_oddEven ) {
+
+ while ( curS ) {
+ int cb = curS->bord;
+ curW++;
+ curW &= 0x00000001;
+ if ( curW == 0 ) {
+ line->AddBord(lastX,swrData[cb].curX,true);
+ } else {
+ lastX = swrData[cb].curX;
+ }
+ curS = static_cast<SweepTree*>(curS->elem[RIGHT]);
+ }
+
+ } else if ( directed == fill_positive ) {
+
+ // doesn't behave correctly; no way i know to do this without a ConvertToShape()
+ while ( curS ) {
+ int cb = curS->bord;
+ int oW = curW;
+ if ( swrData[cb].sens ) {
+ curW++;
+ } else {
+ curW--;
+ }
+
+ if ( curW <= 0 && oW > 0) {
+ line->AddBord(lastX, swrData[cb].curX, true);
+ } else if ( curW > 0 && oW <= 0 ) {
+ lastX = swrData[cb].curX;
+ }
+
+ curS = static_cast<SweepTree*>(curS->elem[RIGHT]);
+ }
+
+ } else if ( directed == fill_nonZero ) {
+
+ while ( curS ) {
+ int cb = curS->bord;
+ int oW = curW;
+ if ( swrData[cb].sens ) {
+ curW++;
+ } else {
+ curW--;
+ }
+
+ if ( curW == 0 && oW != 0) {
+ line->AddBord(lastX,swrData[cb].curX,true);
+ } else if ( curW != 0 && oW == 0 ) {
+ lastX=swrData[cb].curX;
+ }
+ curS = static_cast<SweepTree*>(curS->elem[RIGHT]);
+ }
+ }
+
+ }
+
+ int curPt = curP;
+ while ( curPt < numberOfPoints() && getPoint(curPt).x[1] <= to ) {
+ int nPt = curPt++;
+
+ int cb;
+ int nbUp;
+ int nbDn;
+ int upNo;
+ int dnNo;
+
+ if ( getPoint(nPt).totalDegree() == 2 ) {
+ _countUpDownTotalDegree2(nPt, &nbUp, &nbDn, &upNo, &dnNo);
+ } else {
+ _countUpDown(nPt, &nbUp, &nbDn, &upNo, &dnNo);
+ }
+
+ if ( nbDn <= 0 ) {
+ upNo = -1;
+ }
+ if ( upNo >= 0 && swrData[upNo].misc == nullptr ) {
+ upNo = -1;
+ }
+
+ if ( nbUp > 1 || ( nbUp == 1 && upNo < 0 ) ) {
+ int cb = getPoint(nPt).incidentEdge[FIRST];
+ while ( cb >= 0 && cb < numberOfEdges() ) {
+ Shape::dg_arete const &e = getEdge(cb);
+ if ( nPt == std::max(e.st, e.en) ) {
+ if ( cb != upNo ) {
+ SweepTree* node=swrData[cb].misc;
+ if ( node ) {
+ _updateIntersection(cb, nPt);
+ DestroyEdge(cb, line);
+ node->Remove(*sTree,*sEvts,true);
+ }
+ }
+ }
+ cb = NextAt(nPt,cb);
+ }
+ }
+
+ // traitement du "upNo devient dnNo"
+ SweepTree* insertionNode = nullptr;
+ if ( dnNo >= 0 ) {
+ if ( upNo >= 0 ) {
+ SweepTree* node = swrData[upNo].misc;
+ _updateIntersection(upNo, nPt);
+ DestroyEdge(upNo, line);
+
+ node->ConvertTo(this, dnNo, 1, nPt);
+
+ swrData[dnNo].misc = node;
+ insertionNode = node;
+ CreateEdge(dnNo, to, step);
+
+ } else {
+
+ SweepTree* node = sTree->add(this,dnNo,1,nPt,this);
+ swrData[dnNo].misc = node;
+ node->Insert(*sTree, *sEvts, this, nPt, true);
+ insertionNode = node;
+ CreateEdge(dnNo, to, step);
+ }
+ }
+
+ if ( nbDn > 1 ) { // si nbDn == 1 , alors dnNo a deja ete traite
+ cb = getPoint(nPt).incidentEdge[FIRST];
+ while ( cb >= 0 && cb < numberOfEdges() ) {
+ Shape::dg_arete const &e = getEdge(cb);
+ if ( nPt == std::min(e.st, e.en) ) {
+ if ( cb != dnNo ) {
+ SweepTree* node = sTree->add(this, cb, 1, nPt, this);
+ swrData[cb].misc = node;
+ node->InsertAt(*sTree, *sEvts, this, insertionNode, nPt, true);
+ CreateEdge(cb, to, step);
+ }
+ }
+ cb = NextAt(nPt, cb);
+ }
+ }
+ }
+
+ curP = curPt;
+ if ( curPt > 0 ) {
+ pos = getPoint(curPt - 1).x[1];
+ } else {
+ pos = to;
+ }
+
+ pos = to;
+ if ( sTree->racine ) {
+ SweepTree* curS = static_cast<SweepTree*>(sTree->racine->Leftmost());
+ while ( curS ) {
+ int cb = curS->bord;
+ AvanceEdge(cb, to, line, exact, step);
+ curS = static_cast<SweepTree*>(curS->elem[RIGHT]);
+ }
+ }
+}
+
+
+void Shape::Scan(float &pos, int &curP, float to, AlphaLigne *line, bool exact, float step)
+{
+ if ( numberOfEdges() <= 1 ) {
+ return;
+ }
+
+ if ( pos >= to ) {
+ return;
+ }
+
+ int curPt = curP;
+ while ( curPt < numberOfPoints() && getPoint(curPt).x[1] <= to ) {
+ int nPt = curPt++;
+
+ int nbUp;
+ int nbDn;
+ int upNo;
+ int dnNo;
+ if ( getPoint(nPt).totalDegree() == 2 ) {
+ _countUpDownTotalDegree2(nPt, &nbUp, &nbDn, &upNo, &dnNo);
+ } else {
+ _countUpDown(nPt, &nbUp, &nbDn, &upNo, &dnNo);
+ }
+
+ if ( nbDn <= 0 ) {
+ upNo=-1;
+ }
+ if ( upNo >= 0 && swrData[upNo].misc == nullptr ) {
+ upNo=-1;
+ }
+
+ if ( nbUp > 1 || ( nbUp == 1 && upNo < 0 ) ) {
+ int cb = getPoint(nPt).incidentEdge[FIRST];
+ while ( cb >= 0 && cb < numberOfEdges() ) {
+ Shape::dg_arete const &e = getEdge(cb);
+ if ( nPt == std::max(e.st, e.en) ) {
+ if ( cb != upNo ) {
+ SweepTree* node = swrData[cb].misc;
+ if ( node ) {
+ _updateIntersection(cb, nPt);
+ DestroyEdge(cb, line);
+ node->Remove(*sTree, *sEvts, true);
+ }
+ }
+ }
+
+ cb = NextAt(nPt,cb);
+ }
+ }
+
+ // traitement du "upNo devient dnNo"
+ SweepTree* insertionNode = nullptr;
+ if ( dnNo >= 0 ) {
+ if ( upNo >= 0 ) {
+ SweepTree* node = swrData[upNo].misc;
+ _updateIntersection(upNo, nPt);
+ DestroyEdge(upNo, line);
+
+ node->ConvertTo(this, dnNo, 1, nPt);
+
+ swrData[dnNo].misc = node;
+ insertionNode = node;
+ CreateEdge(dnNo, to, step);
+ swrData[dnNo].guess = swrData[upNo].guess;
+ } else {
+ SweepTree* node = sTree->add(this, dnNo, 1, nPt, this);
+ swrData[dnNo].misc = node;
+ node->Insert(*sTree, *sEvts, this, nPt, true);
+ insertionNode = node;
+ CreateEdge(dnNo, to, step);
+ }
+ }
+
+ if ( nbDn > 1 ) { // si nbDn == 1 , alors dnNo a deja ete traite
+ int cb = getPoint(nPt).incidentEdge[FIRST];
+ while ( cb >= 0 && cb < numberOfEdges() ) {
+ Shape::dg_arete const &e = getEdge(cb);
+ if ( nPt == std::min(e.st, e.en) ) {
+ if ( cb != dnNo ) {
+ SweepTree* node = sTree->add(this, cb, 1, nPt, this);
+ swrData[cb].misc = node;
+ node->InsertAt(*sTree, *sEvts, this, insertionNode, nPt, true);
+ CreateEdge(cb, to, step);
+ }
+ }
+ cb = NextAt(nPt,cb);
+ }
+ }
+ }
+
+ curP = curPt;
+ if ( curPt > 0 ) {
+ pos = getPoint(curPt - 1).x[1];
+ } else {
+ pos = to;
+ }
+
+ pos = to;
+ if ( sTree->racine ) {
+ SweepTree* curS = static_cast<SweepTree*>(sTree->racine->Leftmost());
+ while ( curS ) {
+ int cb = curS->bord;
+ AvanceEdge(cb, to, line, exact, step);
+ curS = static_cast<SweepTree*>(curS->elem[RIGHT]);
+ }
+ }
+}
+
+
+
+void Shape::QuickScan(float &pos, int &curP, float to, FloatLigne* line, float step)
+{
+ if ( numberOfEdges() <= 1 ) {
+ return;
+ }
+
+ if ( pos >= to ) {
+ return;
+ }
+
+ if ( nbQRas > 1 ) {
+ int curW = 0;
+ // float lastX = 0;
+ // float lastY = 0;
+ int lastGuess = -1;
+ int lastB = -1;
+
+ for (int i = firstQRas; i >= 0 && i < nbQRas; i = qrsData[i].next) {
+ int cb = qrsData[i].bord;
+ int oW = curW;
+ if ( swrData[cb].sens ) {
+ curW++;
+ } else {
+ curW--;
+ }
+
+ if ( curW % 2 == 0 && oW % 2 != 0) {
+
+ lastGuess = line->AppendBord(swrData[lastB].curX,
+ to - swrData[lastB].curY,
+ swrData[cb].curX,
+ to - swrData[cb].curY,
+ 0.0);
+
+ swrData[cb].guess = lastGuess;
+ if ( lastB >= 0 ) {
+ swrData[lastB].guess = lastGuess - 1;
+ }
+
+ } else if ( curW%2 != 0 && oW%2 == 0 ) {
+
+ // lastX = swrData[cb].curX;
+ // lastY = swrData[cb].curY;
+ lastB = cb;
+ swrData[cb].guess = -1;
+
+ } else {
+ swrData[cb].guess = -1;
+ }
+ }
+ }
+
+ int curPt = curP;
+ while ( curPt < numberOfPoints() && getPoint(curPt).x[1] <= to ) {
+ int nPt = curPt++;
+
+ int nbUp;
+ int nbDn;
+ int upNo;
+ int dnNo;
+ if ( getPoint(nPt).totalDegree() == 2 ) {
+ _countUpDownTotalDegree2(nPt, &nbUp, &nbDn, &upNo, &dnNo);
+ } else {
+ _countUpDown(nPt, &nbUp, &nbDn, &upNo, &dnNo);
+ }
+
+ if ( nbDn <= 0 ) {
+ upNo = -1;
+ }
+ if ( upNo >= 0 && swrData[upNo].misc == nullptr ) {
+ upNo = -1;
+ }
+
+ if ( nbUp > 1 || ( nbUp == 1 && upNo < 0 ) ) {
+ int cb = getPoint(nPt).incidentEdge[FIRST];
+ while ( cb >= 0 && cb < numberOfEdges() ) {
+ Shape::dg_arete const &e = getEdge(cb);
+ if ( nPt == std::max(e.st, e.en) ) {
+ if ( cb != upNo ) {
+ QuickRasterSubEdge(cb);
+ _updateIntersection(cb, nPt);
+ DestroyEdge(cb, to, line);
+ }
+ }
+ cb = NextAt(nPt, cb);
+ }
+ }
+
+ // traitement du "upNo devient dnNo"
+ int ins_guess=-1;
+ if ( dnNo >= 0 ) {
+ if ( upNo >= 0 ) {
+ ins_guess = QuickRasterChgEdge(upNo ,dnNo, getPoint(nPt).x[0]);
+ _updateIntersection(upNo, nPt);
+ DestroyEdge(upNo, to, line);
+
+ CreateEdge(dnNo, to, step);
+ swrData[dnNo].guess = swrData[upNo].guess;
+ } else {
+ ins_guess = QuickRasterAddEdge(dnNo, getPoint(nPt).x[0], ins_guess);
+ CreateEdge(dnNo, to, step);
+ }
+ }
+
+ if ( nbDn > 1 ) { // si nbDn == 1 , alors dnNo a deja ete traite
+ int cb = getPoint(nPt).incidentEdge[FIRST];
+ while ( cb >= 0 && cb < numberOfEdges() ) {
+ Shape::dg_arete const &e = getEdge(cb);
+ if ( nPt == std::min(e.st, e.en) ) {
+ if ( cb != dnNo ) {
+ ins_guess = QuickRasterAddEdge(cb, getPoint(nPt).x[0], ins_guess);
+ CreateEdge(cb, to, step);
+ }
+ }
+ cb = NextAt(nPt, cb);
+ }
+ }
+ }
+
+ curP = curPt;
+ if ( curPt > 0 ) {
+ pos = getPoint(curPt-1).x[1];
+ } else {
+ pos=to;
+ }
+
+ pos = to;
+ for (int i=0; i < nbQRas; i++) {
+ int cb = qrsData[i].bord;
+ AvanceEdge(cb, to, line, true, step);
+ qrsData[i].x = swrData[cb].curX;
+ }
+
+ QuickRasterSort();
+}
+
+
+
+
+void Shape::QuickScan(float &pos, int &curP, float to, FillRule directed, BitLigne* line, float step)
+{
+ if ( numberOfEdges() <= 1 ) {
+ return;
+ }
+
+ if ( pos >= to ) {
+ return;
+ }
+
+ if ( nbQRas > 1 ) {
+ int curW = 0;
+ float lastX = 0;
+
+ if ( directed == fill_oddEven ) {
+
+ for (int i = firstQRas; i >= 0 && i < nbQRas; i = qrsData[i].next) {
+ int cb = qrsData[i].bord;
+ curW++;
+ curW &= 1;
+ if ( curW == 0 ) {
+ line->AddBord(lastX, swrData[cb].curX, true);
+ } else {
+ lastX = swrData[cb].curX;
+ }
+ }
+
+ } else if ( directed == fill_positive ) {
+ // doesn't behave correctly; no way i know to do this without a ConvertToShape()
+ for (int i = firstQRas; i >= 0 && i < nbQRas; i = qrsData[i].next) {
+ int cb = qrsData[i].bord;
+ int oW = curW;
+ if ( swrData[cb].sens ) {
+ curW++;
+ } else {
+ curW--;
+ }
+
+ if ( curW <= 0 && oW > 0) {
+ line->AddBord(lastX, swrData[cb].curX, true);
+ } else if ( curW > 0 && oW <= 0 ) {
+ lastX = swrData[cb].curX;
+ }
+ }
+
+ } else if ( directed == fill_nonZero ) {
+ for (int i = firstQRas; i >= 0 && i < nbQRas; i = qrsData[i].next) {
+ int cb = qrsData[i].bord;
+ int oW = curW;
+ if ( swrData[cb].sens ) {
+ curW++;
+ } else {
+ curW--;
+ }
+
+ if ( curW == 0 && oW != 0) {
+ line->AddBord(lastX, swrData[cb].curX, true);
+ } else if ( curW != 0 && oW == 0 ) {
+ lastX = swrData[cb].curX;
+ }
+ }
+ }
+ }
+
+ int curPt = curP;
+ while ( curPt < numberOfPoints() && getPoint(curPt).x[1] <= to ) {
+ int nPt = curPt++;
+
+ int nbUp;
+ int nbDn;
+ int upNo;
+ int dnNo;
+ if ( getPoint(nPt).totalDegree() == 2 ) {
+ _countUpDownTotalDegree2(nPt, &nbUp, &nbDn, &upNo, &dnNo);
+ } else {
+ _countUpDown(nPt, &nbUp, &nbDn, &upNo, &dnNo);
+ }
+
+ if ( nbDn <= 0 ) {
+ upNo = -1;
+ }
+
+ if ( upNo >= 0 && swrData[upNo].misc == nullptr ) {
+ upNo = -1;
+ }
+
+ if ( nbUp > 1 || ( nbUp == 1 && upNo < 0 ) ) {
+ int cb = getPoint(nPt).incidentEdge[FIRST];
+ while ( cb >= 0 && cb < numberOfEdges() ) {
+ Shape::dg_arete const &e = getEdge(cb);
+ if ( nPt == std::max(e.st, e.en) ) {
+ if ( cb != upNo ) {
+ QuickRasterSubEdge(cb);
+ _updateIntersection(cb, nPt);
+ DestroyEdge(cb, line);
+ }
+ }
+ cb = NextAt(nPt, cb);
+ }
+ }
+
+ // traitement du "upNo devient dnNo"
+ int ins_guess = -1;
+ if ( dnNo >= 0 ) {
+ if ( upNo >= 0 ) {
+ ins_guess = QuickRasterChgEdge(upNo, dnNo, getPoint(nPt).x[0]);
+ _updateIntersection(upNo, nPt);
+ DestroyEdge(upNo, line);
+
+ CreateEdge(dnNo, to, step);
+ } else {
+ ins_guess = QuickRasterAddEdge(dnNo, getPoint(nPt).x[0], ins_guess);
+ CreateEdge(dnNo, to, step);
+ }
+ }
+
+ if ( nbDn > 1 ) { // si nbDn == 1 , alors dnNo a deja ete traite
+ int cb = getPoint(nPt).incidentEdge[FIRST];
+ while ( cb >= 0 && cb < numberOfEdges() ) {
+ Shape::dg_arete const &e = getEdge(cb);
+ if ( nPt == std::min(e.st, e.en) ) {
+ if ( cb != dnNo ) {
+ ins_guess = QuickRasterAddEdge(cb, getPoint(nPt).x[0], ins_guess);
+ CreateEdge(cb, to, step);
+ }
+ }
+ cb = NextAt(nPt,cb);
+ }
+ }
+ }
+
+ curP = curPt;
+ if ( curPt > 0 ) {
+ pos=getPoint(curPt - 1).x[1];
+ } else {
+ pos = to;
+ }
+
+ pos = to;
+ for (int i = 0; i < nbQRas; i++) {
+ int cb = qrsData[i].bord;
+ AvanceEdge(cb, to, line, true, step);
+ qrsData[i].x = swrData[cb].curX;
+ }
+
+ QuickRasterSort();
+}
+
+
+
+void Shape::QuickScan(float &pos, int &curP, float to, AlphaLigne* line, float step)
+{
+ if ( numberOfEdges() <= 1 ) {
+ return;
+ }
+ if ( pos >= to ) {
+ return;
+ }
+
+ int curPt = curP;
+ while ( curPt < numberOfPoints() && getPoint(curPt).x[1] <= to ) {
+ int nPt = curPt++;
+
+ int nbUp;
+ int nbDn;
+ int upNo;
+ int dnNo;
+ if ( getPoint(nPt).totalDegree() == 2 ) {
+ _countUpDownTotalDegree2(nPt, &nbUp, &nbDn, &upNo, &dnNo);
+ } else {
+ _countUpDown(nPt, &nbUp, &nbDn, &upNo, &dnNo);
+ }
+
+ if ( nbDn <= 0 ) {
+ upNo = -1;
+ }
+ if ( upNo >= 0 && swrData[upNo].misc == nullptr ) {
+ upNo = -1;
+ }
+
+ if ( nbUp > 1 || ( nbUp == 1 && upNo < 0 ) ) {
+ int cb = getPoint(nPt).incidentEdge[FIRST];
+ while ( cb >= 0 && cb < numberOfEdges() ) {
+ Shape::dg_arete const &e = getEdge(cb);
+ if ( nPt == std::max(e.st, e.en) ) {
+ if ( cb != upNo ) {
+ QuickRasterSubEdge(cb);
+ _updateIntersection(cb, nPt);
+ DestroyEdge(cb, line);
+ }
+ }
+ cb = NextAt(nPt,cb);
+ }
+ }
+
+ // traitement du "upNo devient dnNo"
+ int ins_guess = -1;
+ if ( dnNo >= 0 ) {
+ if ( upNo >= 0 ) {
+ ins_guess = QuickRasterChgEdge(upNo, dnNo, getPoint(nPt).x[0]);
+ _updateIntersection(upNo, nPt);
+ DestroyEdge(upNo, line);
+
+ CreateEdge(dnNo, to, step);
+ swrData[dnNo].guess = swrData[upNo].guess;
+ } else {
+ ins_guess = QuickRasterAddEdge(dnNo, getPoint(nPt).x[0], ins_guess);
+ CreateEdge(dnNo, to, step);
+ }
+ }
+
+ if ( nbDn > 1 ) { // si nbDn == 1 , alors dnNo a deja ete traite
+ int cb = getPoint(nPt).incidentEdge[FIRST];
+ while ( cb >= 0 && cb < numberOfEdges() ) {
+ Shape::dg_arete const &e = getEdge(cb);
+ if ( nPt == std::min(e.st, e.en) ) {
+ if ( cb != dnNo ) {
+ ins_guess = QuickRasterAddEdge(cb,getPoint(nPt).x[0], ins_guess);
+ CreateEdge(cb, to, step);
+ }
+ }
+ cb = NextAt(nPt,cb);
+ }
+ }
+ }
+
+ curP = curPt;
+ if ( curPt > 0 ) {
+ pos = getPoint(curPt-1).x[1];
+ } else {
+ pos = to;
+ }
+
+ pos = to;
+ for (int i = 0; i < nbQRas; i++) {
+ int cb = qrsData[i].bord;
+ AvanceEdge(cb, to, line, true, step);
+ qrsData[i].x = swrData[cb].curX;
+ }
+
+ QuickRasterSort();
+}
+
+
+/*
+ * operations de bases pour la rasterization
+ *
+ */
+void Shape::CreateEdge(int no, float to, float step)
+{
+ int cPt;
+ Geom::Point dir;
+ if ( getEdge(no).st < getEdge(no).en ) {
+ cPt = getEdge(no).st;
+ swrData[no].sens = true;
+ dir = getEdge(no).dx;
+ } else {
+ cPt = getEdge(no).en;
+ swrData[no].sens = false;
+ dir = -getEdge(no).dx;
+ }
+
+ swrData[no].lastX = swrData[no].curX = getPoint(cPt).x[0];
+ swrData[no].lastY = swrData[no].curY = getPoint(cPt).x[1];
+
+ if ( fabs(dir[1]) < 0.000001 ) {
+ swrData[no].dxdy = 0;
+ } else {
+ swrData[no].dxdy = dir[0]/dir[1];
+ }
+
+ if ( fabs(dir[0]) < 0.000001 ) {
+ swrData[no].dydx = 0;
+ } else {
+ swrData[no].dydx = dir[1]/dir[0];
+ }
+
+ swrData[no].calcX = swrData[no].curX + (to - step - swrData[no].curY) * swrData[no].dxdy;
+ swrData[no].guess = -1;
+}
+
+
+void Shape::AvanceEdge(int no, float to, bool exact, float step)
+{
+ if ( exact ) {
+ Geom::Point dir;
+ Geom::Point stp;
+ if ( swrData[no].sens ) {
+ stp = getPoint(getEdge(no).st).x;
+ dir = getEdge(no).dx;
+ } else {
+ stp = getPoint(getEdge(no).en).x;
+ dir = -getEdge(no).dx;
+ }
+
+ if ( fabs(dir[1]) < 0.000001 ) {
+ swrData[no].calcX = stp[0] + dir[0];
+ } else {
+ swrData[no].calcX = stp[0] + ((to - stp[1]) * dir[0]) / dir[1];
+ }
+ } else {
+ swrData[no].calcX += step * swrData[no].dxdy;
+ }
+
+ swrData[no].lastX = swrData[no].curX;
+ swrData[no].lastY = swrData[no].curY;
+ swrData[no].curX = swrData[no].calcX;
+ swrData[no].curY = to;
+}
+
+/*
+ * specialisation par type de structure utilise
+ */
+
+void Shape::DestroyEdge(int no, float to, FloatLigne* line)
+{
+ if ( swrData[no].sens ) {
+
+ if ( swrData[no].curX < swrData[no].lastX ) {
+
+ swrData[no].guess = line->AddBordR(swrData[no].curX,
+ to - swrData[no].curY,
+ swrData[no].lastX,
+ to - swrData[no].lastY,
+ -swrData[no].dydx,
+ swrData[no].guess);
+
+ } else if ( swrData[no].curX > swrData[no].lastX ) {
+
+ swrData[no].guess = line->AddBord(swrData[no].lastX,
+ -(to - swrData[no].lastY),
+ swrData[no].curX,
+ -(to - swrData[no].curY),
+ swrData[no].dydx,
+ swrData[no].guess);
+ }
+
+ } else {
+
+ if ( swrData[no].curX < swrData[no].lastX ) {
+
+ swrData[no].guess = line->AddBordR(swrData[no].curX,
+ -(to - swrData[no].curY),
+ swrData[no].lastX,
+ -(to - swrData[no].lastY),
+ swrData[no].dydx,
+ swrData[no].guess);
+
+ } else if ( swrData[no].curX > swrData[no].lastX ) {
+
+ swrData[no].guess = line->AddBord(swrData[no].lastX,
+ to - swrData[no].lastY,
+ swrData[no].curX,
+ to - swrData[no].curY,
+ -swrData[no].dydx,
+ swrData[no].guess);
+ }
+ }
+}
+
+
+
+void Shape::AvanceEdge(int no, float to, FloatLigne *line, bool exact, float step)
+{
+ AvanceEdge(no,to,exact,step);
+
+ if ( swrData[no].sens ) {
+
+ if ( swrData[no].curX < swrData[no].lastX ) {
+
+ swrData[no].guess = line->AddBordR(swrData[no].curX,
+ to - swrData[no].curY,
+ swrData[no].lastX,
+ to - swrData[no].lastY,
+ -swrData[no].dydx,
+ swrData[no].guess);
+
+ } else if ( swrData[no].curX > swrData[no].lastX ) {
+
+ swrData[no].guess = line->AddBord(swrData[no].lastX,
+ -(to - swrData[no].lastY),
+ swrData[no].curX,
+ -(to - swrData[no].curY),
+ swrData[no].dydx,
+ swrData[no].guess);
+ }
+
+ } else {
+
+ if ( swrData[no].curX < swrData[no].lastX ) {
+
+ swrData[no].guess = line->AddBordR(swrData[no].curX,
+ -(to - swrData[no].curY),
+ swrData[no].lastX,
+ -(to - swrData[no].lastY),
+ swrData[no].dydx,
+ swrData[no].guess);
+
+ } else if ( swrData[no].curX > swrData[no].lastX ) {
+
+ swrData[no].guess = line->AddBord(swrData[no].lastX,
+ to - swrData[no].lastY,
+ swrData[no].curX,
+ to - swrData[no].curY,
+ -swrData[no].dydx,
+ swrData[no].guess);
+ }
+ }
+}
+
+
+void Shape::DestroyEdge(int no, BitLigne *line)
+{
+ if ( swrData[no].sens ) {
+
+ if ( swrData[no].curX < swrData[no].lastX ) {
+
+ line->AddBord(swrData[no].curX, swrData[no].lastX, false);
+
+ } else if ( swrData[no].curX > swrData[no].lastX ) {
+
+ line->AddBord(swrData[no].lastX,swrData[no].curX,false);
+ }
+
+ } else {
+
+ if ( swrData[no].curX < swrData[no].lastX ) {
+
+ line->AddBord(swrData[no].curX, swrData[no].lastX, false);
+
+ } else if ( swrData[no].curX > swrData[no].lastX ) {
+
+ line->AddBord(swrData[no].lastX, swrData[no].curX, false);
+
+ }
+ }
+}
+
+
+void Shape::AvanceEdge(int no, float to, BitLigne *line, bool exact, float step)
+{
+ AvanceEdge(no, to, exact, step);
+
+ if ( swrData[no].sens ) {
+
+ if ( swrData[no].curX < swrData[no].lastX ) {
+
+ line->AddBord(swrData[no].curX, swrData[no].lastX, false);
+
+ } else if ( swrData[no].curX > swrData[no].lastX ) {
+
+ line->AddBord(swrData[no].lastX, swrData[no].curX, false);
+ }
+
+ } else {
+
+ if ( swrData[no].curX < swrData[no].lastX ) {
+
+ line->AddBord(swrData[no].curX, swrData[no].lastX, false);
+
+ } else if ( swrData[no].curX > swrData[no].lastX ) {
+
+ line->AddBord(swrData[no].lastX, swrData[no].curX, false);
+ }
+ }
+}
+
+
+void Shape::DestroyEdge(int no, AlphaLigne* line)
+{
+ if ( swrData[no].sens ) {
+
+ if ( swrData[no].curX <= swrData[no].lastX ) {
+
+ line->AddBord(swrData[no].curX,
+ 0,
+ swrData[no].lastX,
+ swrData[no].curY - swrData[no].lastY,
+ -swrData[no].dydx);
+
+ } else if ( swrData[no].curX > swrData[no].lastX ) {
+
+ line->AddBord(swrData[no].lastX,
+ 0,
+ swrData[no].curX,
+ swrData[no].curY - swrData[no].lastY,
+ swrData[no].dydx);
+ }
+
+ } else {
+
+ if ( swrData[no].curX <= swrData[no].lastX ) {
+
+ line->AddBord(swrData[no].curX,
+ 0,
+ swrData[no].lastX,
+ swrData[no].lastY - swrData[no].curY,
+ swrData[no].dydx);
+
+ } else if ( swrData[no].curX > swrData[no].lastX ) {
+
+ line->AddBord(swrData[no].lastX,
+ 0,
+ swrData[no].curX,
+ swrData[no].lastY - swrData[no].curY,
+ -swrData[no].dydx);
+ }
+ }
+}
+
+
+void Shape::AvanceEdge(int no, float to, AlphaLigne *line, bool exact, float step)
+{
+ AvanceEdge(no,to,exact,step);
+
+ if ( swrData[no].sens ) {
+
+ if ( swrData[no].curX <= swrData[no].lastX ) {
+
+ line->AddBord(swrData[no].curX,
+ 0,
+ swrData[no].lastX,
+ swrData[no].curY - swrData[no].lastY,
+ -swrData[no].dydx);
+
+ } else if ( swrData[no].curX > swrData[no].lastX ) {
+
+ line->AddBord(swrData[no].lastX,
+ 0,
+ swrData[no].curX,
+ swrData[no].curY - swrData[no].lastY,
+ swrData[no].dydx);
+ }
+
+ } else {
+
+ if ( swrData[no].curX <= swrData[no].lastX ) {
+
+ line->AddBord(swrData[no].curX,
+ 0,
+ swrData[no].lastX,
+ swrData[no].lastY - swrData[no].curY,
+ swrData[no].dydx);
+
+ } else if ( swrData[no].curX > swrData[no].lastX ) {
+
+ line->AddBord(swrData[no].lastX,
+ 0,
+ swrData[no].curX,
+ swrData[no].lastY - swrData[no].curY,
+ -swrData[no].dydx);
+ }
+ }
+}
+
+/**
+ * \param P point index.
+ * \param numberUp Filled in with the number of edges coming into P from above.
+ * \param numberDown Filled in with the number of edges coming exiting P to go below.
+ * \param upEdge One of the numberUp edges, or -1.
+ * \param downEdge One of the numberDown edges, or -1.
+ */
+
+void Shape::_countUpDown(int P, int *numberUp, int *numberDown, int *upEdge, int *downEdge) const
+{
+ *numberUp = 0;
+ *numberDown = 0;
+ *upEdge = -1;
+ *downEdge = -1;
+
+ int i = getPoint(P).incidentEdge[FIRST];
+
+ while ( i >= 0 && i < numberOfEdges() ) {
+ Shape::dg_arete const &e = getEdge(i);
+ if ( P == std::max(e.st, e.en) ) {
+ *upEdge = i;
+ (*numberUp)++;
+ }
+ if ( P == std::min(e.st, e.en) ) {
+ *downEdge = i;
+ (*numberDown)++;
+ }
+ i = NextAt(P, i);
+ }
+
+}
+
+
+
+/**
+ * Version of Shape::_countUpDown optimised for the case when getPoint(P).totalDegree() == 2.
+ */
+
+void Shape::_countUpDownTotalDegree2(int P,
+ int *numberUp, int *numberDown, int *upEdge, int *downEdge) const
+{
+ *numberUp = 0;
+ *numberDown = 0;
+ *upEdge = -1;
+ *downEdge = -1;
+
+ for (int j : getPoint(P).incidentEdge) {
+ Shape::dg_arete const &e = getEdge(j);
+ if ( P == std::max(e.st, e.en) ) {
+ *upEdge = j;
+ (*numberUp)++;
+ }
+ if ( P == std::min(e.st, e.en) ) {
+ *downEdge = j;
+ (*numberDown)++;
+ }
+ }
+}
+
+
+void Shape::_updateIntersection(int e, int p)
+{
+ swrData[e].lastX = swrData[e].curX;
+ swrData[e].lastY = swrData[e].curY;
+ swrData[e].curX = getPoint(p).x[0];
+ swrData[e].curY = getPoint(p).x[1];
+ swrData[e].misc = nullptr;
+}
+
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/livarot/ShapeSweep.cpp b/src/livarot/ShapeSweep.cpp
new file mode 100644
index 0000000..c139d80
--- /dev/null
+++ b/src/livarot/ShapeSweep.cpp
@@ -0,0 +1,3637 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * TODO: insert short description here
+ *//*
+ * Authors:
+ * see git history
+ * Fred
+ *
+ * Copyright (C) 2018 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+#include <glib.h>
+#include <2geom/affine.h>
+#include "Shape.h"
+#include "livarot/sweep-event-queue.h"
+#include "livarot/sweep-tree-list.h"
+#include "livarot/sweep-tree.h"
+
+//int doDebug=0;
+
+/*
+ * El Intersector.
+ * algorithm: 1) benley ottman to get intersections of all the polygon's edges
+ * 2) rounding of the points of the polygon, Hooby's algorithm
+ * 3) DFS with clockwise choice of the edge to compute the windings
+ * 4) choose edges according to winding numbers and fill rule
+ * some additional nastyness: step 2 needs a seed winding number for the upper-left point of each
+ * connex subgraph of the graph. computing these brutally is O(n^3): baaaad. so during the sweeping in 1)
+ * we keep for each point the edge of the resulting graph (not the original) that lies just on its left;
+ * when the time comes for the point to get its winding number computed, that edge must have been treated,
+ * because its upper end lies above the aforementioned point, meaning we know the winding number of the point.
+ * only, there is a catch: since we're sweeping the polygon, the edge we want to link the point to has not yet been
+ * added (that would be too easy...). so the points are put on a linked list on the original shape's edge, and the list
+ * is flushed when the edge is added.
+ * rounding: to do the rounding, we need to find which edges cross the surrounding of the rounded points (at
+ * each sweepline position). grunt method tries all combination of "rounded points in the sweepline"x"edges crossing
+ * the sweepline". That's bad (and that's what polyboolean does, if i am not mistaken). so for each point
+ * rounded in a given sweepline, keep immediate left and right edges at the time the point is treated.
+ * when edges/points crossing are searched, walk the edge list (in the sweepline at the end of the batch) starting
+ * from the rounded points' left and right from that time. may sound strange, but it works because edges that
+ * end or start in the batch have at least one end in the batch.
+ * all these are the cause of the numerous linked lists of points and edges maintained in the sweeping
+ */
+
+void
+Shape::ResetSweep ()
+{
+ MakePointData (true);
+ MakeEdgeData (true);
+ MakeSweepSrcData (true);
+}
+
+void
+Shape::CleanupSweep ()
+{
+ MakePointData (false);
+ MakeEdgeData (false);
+ MakeSweepSrcData (false);
+}
+
+void
+Shape::ForceToPolygon ()
+{
+ type = shape_polygon;
+}
+
+int
+Shape::Reoriente (Shape * a)
+{
+ Reset (0, 0);
+ if (a->numberOfPoints() <= 1 || a->numberOfEdges() <= 1)
+ return 0;
+ if (directedEulerian(a) == false)
+ return shape_input_err;
+
+ _pts = a->_pts;
+ if (numberOfPoints() > maxPt)
+ {
+ maxPt = numberOfPoints();
+ if (_has_points_data) {
+ pData.resize(maxPt);
+ _point_data_initialised = false;
+ _bbox_up_to_date = false;
+ }
+ }
+
+ _aretes = a->_aretes;
+ if (numberOfEdges() > maxAr)
+ {
+ maxAr = numberOfEdges();
+ if (_has_edges_data)
+ eData.resize(maxAr);
+ if (_has_sweep_src_data)
+ swsData.resize(maxAr);
+ if (_has_sweep_dest_data)
+ swdData.resize(maxAr);
+ if (_has_raster_data)
+ swrData.resize(maxAr);
+ }
+
+ MakePointData (true);
+ MakeEdgeData (true);
+ MakeSweepDestData (true);
+
+ initialisePointData();
+
+ for (int i = 0; i < numberOfPoints(); i++) {
+ _pts[i].x = pData[i].rx;
+ _pts[i].oldDegree = getPoint(i).totalDegree();
+ }
+
+ for (int i = 0; i < a->numberOfEdges(); i++)
+ {
+ eData[i].rdx = pData[getEdge(i).en].rx - pData[getEdge(i).st].rx;
+ eData[i].weight = 1;
+ _aretes[i].dx = eData[i].rdx;
+ }
+
+ SortPointsRounded ();
+
+ _need_edges_sorting = true;
+ GetWindings (this, nullptr, bool_op_union, true);
+
+// Plot(341,56,8,400,400,true,true,false,true);
+ for (int i = 0; i < numberOfEdges(); i++)
+ {
+ swdData[i].leW %= 2;
+ swdData[i].riW %= 2;
+ if (swdData[i].leW < 0)
+ swdData[i].leW = -swdData[i].leW;
+ if (swdData[i].riW < 0)
+ swdData[i].riW = -swdData[i].riW;
+ if (swdData[i].leW > 0 && swdData[i].riW <= 0)
+ {
+ eData[i].weight = 1;
+ }
+ else if (swdData[i].leW <= 0 && swdData[i].riW > 0)
+ {
+ Inverse (i);
+ eData[i].weight = 1;
+ }
+ else
+ {
+ eData[i].weight = 0;
+ SubEdge (i);
+ i--;
+ }
+ }
+
+ MakePointData (false);
+ MakeEdgeData (false);
+ MakeSweepDestData (false);
+
+ if (directedEulerian(this) == false)
+ {
+// printf( "pas euclidian2");
+ _pts.clear();
+ _aretes.clear();
+ return shape_euler_err;
+ }
+
+ type = shape_polygon;
+ return 0;
+}
+
+int
+Shape::ConvertToShape (Shape * a, FillRule directed, bool invert)
+{
+ // reset any existing stuff in this shape
+ Reset (0, 0);
+
+ // nothing to do with 0/1 points/edges
+ if (a->numberOfPoints() <= 1 || a->numberOfEdges() <= 1) {
+ return 0;
+ }
+
+ // if shape is not eulerian, we can't proceedA, why though you might ask?
+ // I think because if it's not eulerian, winding numbers won't be consistent and thus
+ // nothing else will work
+ if ( directed != fill_justDont && directedEulerian(a) == false ) {
+ g_warning ("Shape error in ConvertToShape: directedEulerian(a) == false\n");
+ return shape_input_err;
+ }
+
+ a->ResetSweep();
+
+ // allocating the sweepline data structures
+ if (sTree == nullptr) {
+ sTree = new SweepTreeList(a->numberOfEdges());
+ }
+ if (sEvts == nullptr) {
+ sEvts = new SweepEventQueue(a->numberOfEdges());
+ }
+
+ // make room for stuff and set flags
+ MakePointData(true);
+ MakeEdgeData(true);
+ MakeSweepSrcData(true);
+ MakeSweepDestData(true);
+ MakeBackData(a->_has_back_data);
+
+ // initialize pData and eData arrays, stores rounded points, rounded edge vectors and their lengths
+ a->initialisePointData();
+ a->initialiseEdgeData();
+
+ // sort points of a, top to bottom so we can sweepline
+ a->SortPointsRounded();
+
+ // clear the chgts array
+ chgts.clear();
+
+ double lastChange = a->pData[0].rx[1] - 1.0;
+ int lastChgtPt = 0;
+ int edgeHead = -1;
+ Shape *shapeHead = nullptr;
+
+ clearIncidenceData();
+
+ // index of the current point in shape a
+ int curAPt = 0;
+
+ // as long as there is a point we haven't seen yet or events that we haven't popped yet
+ while (curAPt < a->numberOfPoints() || sEvts->size() > 0) {
+ Geom::Point ptX;
+ double ptL, ptR;
+ SweepTree *intersL = nullptr;
+ SweepTree *intersR = nullptr;
+ int nPt = -1;
+ Shape *ptSh = nullptr;
+ bool isIntersection = false;
+
+ // this block gives us the earliest most point (sweeping top to bottom and left to right)
+ // whether it comes from the intersection event priority queue sEvts or sweepline list sTree.
+ // isIntersection tell us if it's coming from an intersection or from the endpoints list of shape a
+ // ptX contains the actual point itself
+ // ptSh contains the shape from which the point comes (if it does) (shape a always)
+ // nPt is the index of the point if it's coming from a shape
+
+ // is there an intersection event to pop?
+ if (sEvts->peek(intersL, intersR, ptX, ptL, ptR))
+ {
+ // is that intersection event before the current point in shape a? If yes, we pop and process the intersection event otherwise
+ // we process the point in shape a, whichever comes first (sweeping top to bottom) the one with smaller y or smaller x (if same y)
+ if (a->pData[curAPt].pending > 0
+ || (a->pData[curAPt].rx[1] > ptX[1]
+ || (a->pData[curAPt].rx[1] == ptX[1]
+ && a->pData[curAPt].rx[0] > ptX[0])))
+ {
+ // if yes, let's process the intersection point
+ /* FIXME: could just be pop? */
+ sEvts->extract(intersL, intersR, ptX, ptL, ptR);
+ isIntersection = true;
+ }
+ else // otherwise, we process the current point in shape a
+ {
+ nPt = curAPt++; // nPt stores the index of this point in shape a that we are going to process
+ ptSh = a;
+ ptX = ptSh->pData[nPt].rx; // get the rounded version of the current point in ptX
+ isIntersection = false; // not an intersection
+ }
+ }
+ else
+ {
+ nPt = curAPt++;
+ ptSh = a;
+ ptX = ptSh->pData[nPt].rx;
+ isIntersection = false;
+ }
+
+ if (isIntersection == false) // if this is not intersection event and the point has total degree of 0, we have nothing to do
+ {
+ if (ptSh->getPoint(nPt).dI == 0 && ptSh->getPoint(nPt).dO == 0)
+ continue;
+ }
+
+ // wherever the point comes from, we add the rounded version to "this" shape's list of points
+ Geom::Point rPtX;
+ rPtX[0]= Round (ptX[0]);
+ rPtX[1]= Round (ptX[1]);
+ int lastPointNo = AddPoint (rPtX); // lastPointNo is the index of this last rounded point we added
+ pData[lastPointNo].rx = rPtX;
+
+ // this whole block deals with the reconstruction procedure only do this if the y level
+ // changed, we don't need to do this as long as the y level has not changed, note that
+ // sweepline in this algorithm moves top to bottom but also left to right when there are
+ // multiple points at same y better to think of it as the sweepline being slightly slanted such
+ // that it'll hit the left points earlier than the right ones In fact, it's better to visualize
+ // as if we are iterating through set of points top to bottom and left ot right instead of a
+ // sweepline.
+ if (rPtX[1] > lastChange)
+ {
+ // the important thing this function does is that it sorts points and merges any duplicate points
+ // so edges with different points (which were at the same coordinates) would be forced to point
+ // to the same exact point. (useful for cases when multiple edges intersect at the same point)
+ // Sort all points before lastPointNo
+ int lastI = AssemblePoints (lastChgtPt, lastPointNo); // lastI is one more than the index of the last point that
+ // was added by AssemblePoints after sorting and merging
+
+ // after AssemblePoints is done sorting, the newInd variable holds the new index of that point
+
+ // update the leftRnd and rightRnd indexes to newInd while traversing the linked list of
+ // edges and shapes. leftRnd and rightRnd are the left most and right most points of an edge
+ // in the resultant polygon that intersect the sweepline. In all non-horizontal edges, both
+ // would be identical, only when the edge is horizontal, the two will be different since the
+ // sweepline will intersect it at multiple endpoints.
+ // To define it more strictly, you can think of leftRnd and rightRnd as being the left most
+ // and right most point in the final resulted shape ("this" shape) at the y level lastChange.
+ Shape *curSh = shapeHead;
+ int curBo = edgeHead;
+ while (curSh)
+ {
+ curSh->swsData[curBo].leftRnd =
+ pData[curSh->swsData[curBo].leftRnd].newInd;
+ curSh->swsData[curBo].rightRnd =
+ pData[curSh->swsData[curBo].rightRnd].newInd;
+
+ Shape *neSh = curSh->swsData[curBo].nextSh;
+ curBo = curSh->swsData[curBo].nextBo;
+ curSh = neSh;
+ }
+
+ for (auto & chgt : chgts)
+ {
+ chgt.ptNo = pData[chgt.ptNo].newInd; // this updates the ptNo index to the new index, so identical points really merge
+ if (chgt.type == 0)
+ {
+ if (chgt.src->getEdge(chgt.bord).st <
+ chgt.src->getEdge(chgt.bord).en)
+ {
+ chgt.src->swsData[chgt.bord].stPt = // <-- No idea where stPt and enPt are really used
+ chgt.ptNo;
+ }
+ else
+ {
+ chgt.src->swsData[chgt.bord].enPt =
+ chgt.ptNo;
+ }
+ }
+ else if (chgt.type == 1)
+ {
+ if (chgt.src->getEdge(chgt.bord).st >
+ chgt.src->getEdge(chgt.bord).en)
+ {
+ chgt.src->swsData[chgt.bord].stPt =
+ chgt.ptNo;
+ }
+ else
+ {
+ chgt.src->swsData[chgt.bord].enPt =
+ chgt.ptNo;
+ }
+ }
+ }
+
+ // finds if points at the y level "lastChange" lie on top of the edge and if yes, modifies
+ // leftRnd and rightRnd of those edges accordingly
+ CheckAdjacencies (lastI, lastChgtPt, shapeHead, edgeHead);
+
+ // reconstruct the edges
+ CheckEdges (lastI, lastChgtPt, a, nullptr, bool_op_union);
+
+ // for each one of the points in the range lastChgtPt..(lastI-1) and if there are already
+ // points associated to the edge pData[i].askForWindingB, push the current one (i) in the linked
+ // list
+ for (int i = lastChgtPt; i < lastI; i++) {
+ if (pData[i].askForWindingS) {
+ Shape *windS = pData[i].askForWindingS;
+ int windB = pData[i].askForWindingB;
+ pData[i].nextLinkedPoint = windS->swsData[windB].firstLinkedPoint;
+ windS->swsData[windB].firstLinkedPoint = i;
+ }
+ }
+
+ // note that AssemblePoints doesn't even touch lastPointNo, it iterates on all the points before
+ // lastPointNo, so while that range might have shrinked because of duplicate points, lastPointNo
+ // remains where it was, so we check if some points got merged, if yes, lastPointNo is kinda far away
+ // from those points (there are garbage points in between due to merging), so we drag it back
+ if (lastI < lastPointNo) {
+ _pts[lastI] = getPoint(lastPointNo);
+ pData[lastI] = pData[lastPointNo];
+ }
+ // set lastPointNo to lastI (the new index for lastPointNo)
+ lastPointNo = lastI;
+ _pts.resize(lastI + 1); // resize and delete everything beyond lastPointNo, we add 1 cuz lastI is an index so u
+ // add one to convert it to size
+
+ lastChgtPt = lastPointNo; // set lastChgtPt
+ lastChange = rPtX[1]; // set lastChange
+ chgts.clear(); // this chgts array gets cleared up whenever the y of the sweepline changes
+ edgeHead = -1;
+ shapeHead = nullptr;
+ }
+
+
+ // are we processing an intersection point?
+ if (isIntersection)
+ {
+ // printf("(%i %i [%i %i]) ",intersL->bord,intersR->bord,intersL->startPoint,intersR->startPoint);
+ // remove any intersections that have interesL as a RIGHT edge
+ intersL->RemoveEvent (*sEvts, LEFT);
+ // remove any intersections that have intersR as a LEFT edge
+ intersR->RemoveEvent (*sEvts, RIGHT);
+
+ // add the intersection point to the chgts array
+ AddChgt (lastPointNo, lastChgtPt, shapeHead, edgeHead, INTERSECTION,
+ intersL->src, intersL->bord, intersR->src, intersR->bord);
+
+ // swap the edges intersL and intersR with each other (because they swap their intersection point with the sweepline as
+ // you pass the intersection point)
+ intersL->SwapWithRight (*sTree, *sEvts);
+
+ // test intersection of the now left edge with the one on its left
+ TesteIntersection (intersL, LEFT, false);
+ // test intersection of the now right edge with the one on its right
+ TesteIntersection (intersR, RIGHT, false);
+ }
+ else
+ { // we are doing with a point in shape a (not an intersection point)
+ int cb;
+
+ // this whole block:
+ // - Counts how many edges start at the current point (nbDn)
+ // - Counts how many edges end at the current point (nbUp)
+ // - Notes the last edge that starts here (dnNo)
+ // - Notes the last edge that ends here (upNo)
+ int nbUp = 0, nbDn = 0;
+ int upNo = -1, dnNo = -1;
+ cb = ptSh->getPoint(nPt).incidentEdge[FIRST];
+ while (cb >= 0 && cb < ptSh->numberOfEdges())
+ {
+ if ((ptSh->getEdge(cb).st < ptSh->getEdge(cb).en
+ && nPt == ptSh->getEdge(cb).en)
+ || (ptSh->getEdge(cb).st > ptSh->getEdge(cb).en
+ && nPt == ptSh->getEdge(cb).st))
+ {
+ upNo = cb;
+ nbUp++;
+ }
+ if ((ptSh->getEdge(cb).st > ptSh->getEdge(cb).en
+ && nPt == ptSh->getEdge(cb).en)
+ || (ptSh->getEdge(cb).st < ptSh->getEdge(cb).en
+ && nPt == ptSh->getEdge(cb).st))
+ {
+ dnNo = cb;
+ nbDn++;
+ }
+ cb = ptSh->NextAt (nPt, cb);
+ }
+
+ if (nbDn <= 0)
+ {
+ upNo = -1;
+ }
+ if (upNo >= 0 && (SweepTree *) ptSh->swsData[upNo].misc == nullptr)
+ {
+ upNo = -1;
+ }
+
+ bool doWinding = true;
+
+ // these blocks of code do the job of adding/removing edges
+ // however, there are some optimizations which leads to their weird if blocks
+
+ // Is there any edge that ends here?
+ if (nbUp > 0)
+ {
+ cb = ptSh->getPoint(nPt).incidentEdge[FIRST];
+ // for all edges that connect to this point
+ while (cb >= 0 && cb < ptSh->numberOfEdges())
+ { // if the edge ends here
+ if ((ptSh->getEdge(cb).st < ptSh->getEdge(cb).en
+ && nPt == ptSh->getEdge(cb).en)
+ || (ptSh->getEdge(cb).st > ptSh->getEdge(cb).en
+ && nPt == ptSh->getEdge(cb).st))
+ {
+ if (cb != upNo) // if this is not the last edge that ended at this point
+ {
+ SweepTree *node =
+ (SweepTree *) ptSh->swsData[cb].misc; // get the sweepline node for this edge
+ if (node == nullptr)
+ {
+ }
+ else
+ {
+ AddChgt (lastPointNo, lastChgtPt, shapeHead, // add the corresponding remove event in chgts
+ edgeHead, EDGE_REMOVED, node->src, node->bord,
+ nullptr, -1);
+ ptSh->swsData[cb].misc = nullptr;
+
+ int onLeftB = -1, onRightB = -1;
+ Shape *onLeftS = nullptr;
+ Shape *onRightS = nullptr;
+ if (node->elem[LEFT])
+ {
+ onLeftB =
+ (static_cast <
+ SweepTree * >(node->elem[LEFT]))->bord;
+ onLeftS =
+ (static_cast <
+ SweepTree * >(node->elem[LEFT]))->src;
+ }
+ if (node->elem[RIGHT])
+ {
+ onRightB =
+ (static_cast <
+ SweepTree * >(node->elem[RIGHT]))->bord;
+ onRightS =
+ (static_cast <
+ SweepTree * >(node->elem[RIGHT]))->src;
+ }
+
+ node->Remove (*sTree, *sEvts, true); // remove this edge
+ // if there are edges on the left and right and they do not start or end at the current point
+ // test if they interesect with each other (since now this edge just go removed and they are side to side)
+ if (onLeftS && onRightS)
+ {
+ SweepTree *onLeft =
+ (SweepTree *) onLeftS->swsData[onLeftB].
+ misc;
+ if (onLeftS == ptSh
+ && (onLeftS->getEdge(onLeftB).en == nPt
+ || onLeftS->getEdge(onLeftB).st ==
+ nPt))
+ {
+ }
+ else
+ {
+ if (onRightS == ptSh
+ && (onRightS->getEdge(onRightB).en ==
+ nPt
+ || onRightS->getEdge(onRightB).
+ st == nPt))
+ {
+ }
+ else
+ {
+ TesteIntersection (onLeft, RIGHT, false);
+ }
+ }
+ }
+ }
+ }
+ }
+ cb = ptSh->NextAt (nPt, cb);
+ }
+ }
+
+ // traitement du "upNo devient dnNo"
+ SweepTree *insertionNode = nullptr;
+ if (dnNo >= 0) // if there is a last edge that started here
+ {
+ if (upNo >= 0) // and there is a last edge that ended here
+ {
+ SweepTree *node = (SweepTree *) ptSh->swsData[upNo].misc;
+
+ AddChgt (lastPointNo, lastChgtPt, shapeHead, edgeHead, EDGE_REMOVED, // add edge removal event to the list
+ node->src, node->bord, nullptr, -1);
+
+ ptSh->swsData[upNo].misc = nullptr;
+
+ node->RemoveEvents (*sEvts); // remove any events associated with this node
+ node->ConvertTo (ptSh, dnNo, 1, lastPointNo); // convert this node the last edge that got added at this point
+ ptSh->swsData[dnNo].misc = node; // store the sweepline edge tree node at misc for later use
+ TesteIntersection (node, RIGHT, false); // test the interesction of this node with the one on its right
+ TesteIntersection (node, LEFT, false); // test the interesection of this node with the one on its left
+ insertionNode = node; // a variable to keep the pointer to this node for later use
+
+ ptSh->swsData[dnNo].curPoint = lastPointNo; // mark the curPoint in swsData for later use in reconstruction
+ AddChgt (lastPointNo, lastChgtPt, shapeHead, edgeHead, EDGE_INSERTED,
+ node->src, node->bord, nullptr, -1); // add the edge insertion event to chgts
+ }
+ else // if there is a last edge that started at this point but not any that ended
+ {
+ SweepTree *node = sTree->add(ptSh, dnNo, 1, lastPointNo, this); // add this edge
+ ptSh->swsData[dnNo].misc = node; // store in misc too
+ node->Insert (*sTree, *sEvts, this, lastPointNo, true); // insert the node at its right location in the sweepline tree
+ if (doWinding)
+ {
+ SweepTree *myLeft =
+ static_cast < SweepTree * >(node->elem[LEFT]);
+ if (myLeft)
+ {
+ pData[lastPointNo].askForWindingS = myLeft->src;
+ pData[lastPointNo].askForWindingB = myLeft->bord;
+ }
+ else
+ {
+ pData[lastPointNo].askForWindingB = -1;
+ }
+ doWinding = false;
+ }
+ TesteIntersection (node, RIGHT, false); // test intersection of this newly inserted node with the one on its right
+ TesteIntersection (node, LEFT, false); // test intersection of this newly inserted node with the one on its left
+ insertionNode = node; // store insertionNode for later use
+
+ ptSh->swsData[dnNo].curPoint = lastPointNo; // mark the curPoint appropriately
+ AddChgt (lastPointNo, lastChgtPt, shapeHead, edgeHead, EDGE_INSERTED, // add the edge inserted event
+ node->src, node->bord, nullptr, -1);
+ }
+ }
+
+ if (nbDn > 1) // if there are more than 1 edges that start at this point
+ { // si nbDn == 1 , alors dnNo a deja ete traite
+ cb = ptSh->getPoint(nPt).incidentEdge[FIRST];
+ while (cb >= 0 && cb < ptSh->numberOfEdges()) // for all edges that connect to this point
+ {
+ if ((ptSh->getEdge(cb).st > ptSh->getEdge(cb).en
+ && nPt == ptSh->getEdge(cb).en)
+ || (ptSh->getEdge(cb).st < ptSh->getEdge(cb).en
+ && nPt == ptSh->getEdge(cb).st))
+ { // if the edge starts here
+ if (cb != dnNo)
+ {
+ SweepTree *node = sTree->add(ptSh, cb, 1, lastPointNo, this); // add the node to the tree
+ ptSh->swsData[cb].misc = node;
+ node->InsertAt (*sTree, *sEvts, this, insertionNode, // insert it at appropriate position
+ nPt, true);
+ if (doWinding)
+ {
+ SweepTree *myLeft =
+ static_cast < SweepTree * >(node->elem[LEFT]);
+ if (myLeft)
+ {
+ pData[lastPointNo].askForWindingS =
+ myLeft->src;
+ pData[lastPointNo].askForWindingB =
+ myLeft->bord;
+ }
+ else
+ {
+ pData[lastPointNo].askForWindingB = -1;
+ }
+ doWinding = false;
+ }
+ TesteIntersection (node, RIGHT, false); // test intersection of this edge with one on its left
+ TesteIntersection (node, LEFT, false); // test intersection of this edge with one on its right
+
+ ptSh->swsData[cb].curPoint = lastPointNo; // store curPoint in sws
+ AddChgt (lastPointNo, lastChgtPt, shapeHead, // add appropriate edge insertion event in chgts
+ edgeHead, EDGE_INSERTED, node->src, node->bord, nullptr,
+ -1);
+ }
+ }
+ cb = ptSh->NextAt (nPt, cb);
+ }
+ }
+ }
+ }
+ // a code block totally identical to the one found above in loop. Has to be run one last time
+ // so written outside..
+ {
+ int lastI = AssemblePoints (lastChgtPt, numberOfPoints());
+
+
+ Shape *curSh = shapeHead;
+ int curBo = edgeHead;
+ while (curSh)
+ {
+ curSh->swsData[curBo].leftRnd =
+ pData[curSh->swsData[curBo].leftRnd].newInd;
+ curSh->swsData[curBo].rightRnd =
+ pData[curSh->swsData[curBo].rightRnd].newInd;
+
+ Shape *neSh = curSh->swsData[curBo].nextSh;
+ curBo = curSh->swsData[curBo].nextBo;
+ curSh = neSh;
+ }
+
+ for (auto & chgt : chgts)
+ {
+ chgt.ptNo = pData[chgt.ptNo].newInd;
+ if (chgt.type == 0)
+ {
+ if (chgt.src->getEdge(chgt.bord).st <
+ chgt.src->getEdge(chgt.bord).en)
+ {
+ chgt.src->swsData[chgt.bord].stPt = chgt.ptNo;
+ }
+ else
+ {
+ chgt.src->swsData[chgt.bord].enPt = chgt.ptNo;
+ }
+ }
+ else if (chgt.type == 1)
+ {
+ if (chgt.src->getEdge(chgt.bord).st >
+ chgt.src->getEdge(chgt.bord).en)
+ {
+ chgt.src->swsData[chgt.bord].stPt = chgt.ptNo;
+ }
+ else
+ {
+ chgt.src->swsData[chgt.bord].enPt = chgt.ptNo;
+ }
+ }
+ }
+
+ CheckAdjacencies (lastI, lastChgtPt, shapeHead, edgeHead);
+
+ CheckEdges (lastI, lastChgtPt, a, nullptr, bool_op_union);
+
+ for (int i = lastChgtPt; i < lastI; i++)
+ {
+ if (pData[i].askForWindingS)
+ {
+ Shape *windS = pData[i].askForWindingS;
+ int windB = pData[i].askForWindingB;
+ pData[i].nextLinkedPoint = windS->swsData[windB].firstLinkedPoint;
+ windS->swsData[windB].firstLinkedPoint = i;
+ }
+ }
+
+ _pts.resize(lastI);
+
+ edgeHead = -1;
+ shapeHead = nullptr;
+ }
+
+ chgts.clear();
+
+ // Plot (98.0, 112.0, 8.0, 400.0, 400.0, true, true, true, true);
+ // Plot(200.0,200.0,2.0,400.0,400.0,true,true,true,true);
+
+ // AssemblePoints(a);
+
+ // GetAdjacencies(a);
+
+ // MakeAretes(a);
+ clearIncidenceData();
+
+ // deal with doublon edges (edges on top of each other)
+ // we only keep one edge and set its weight equivalent to the net difference of the edges.
+ // If two edges are exactly identical and in same direction their weights add up, if they
+ // are in the opposite direction, weights are subtracted. The other edges are removed.
+ AssembleAretes (directed);
+
+ // Plot (98.0, 112.0, 8.0, 400.0, 400.0, true, true, true, true);
+
+ // store the degrees at this point in time
+ for (int i = 0; i < numberOfPoints(); i++)
+ {
+ _pts[i].oldDegree = getPoint(i).totalDegree();
+ }
+ // Validate();
+
+ _need_edges_sorting = true;
+ if ( directed == fill_justDont ) {
+ SortEdges(); // sorting edges
+ } else {
+ GetWindings (a); // getting winding numbers of the edges
+ }
+ // Plot (98.0, 112.0, 8.0, 400.0, 400.0, true, true, true, true);
+ // if ( doDebug ) {
+ // a->CalcBBox();
+ // a->Plot(a->leftX,a->topY,32.0,0.0,0.0,true,true,true,true,"orig.svg");
+ // Plot(a->leftX,a->topY,32.0,0.0,0.0,true,true,true,true,"winded.svg");
+ // }
+
+ // change edges depending on the fill rule. Decide whether to keep it, invert it or get rid of it
+ if (directed == fill_positive)
+ {
+ if (invert)
+ {
+ for (int i = 0; i < numberOfEdges(); i++)
+ {
+ if (swdData[i].leW < 0 && swdData[i].riW >= 0)
+ {
+ eData[i].weight = 1;
+ }
+ else if (swdData[i].leW >= 0 && swdData[i].riW < 0)
+ {
+ Inverse (i);
+ eData[i].weight = 1;
+ }
+ else
+ {
+ eData[i].weight = 0;
+ SubEdge (i);
+ i--;
+ }
+ }
+ }
+ else
+ {
+ for (int i = 0; i < numberOfEdges(); i++)
+ {
+ if (swdData[i].leW > 0 && swdData[i].riW <= 0)
+ {
+ eData[i].weight = 1;
+ }
+ else if (swdData[i].leW <= 0 && swdData[i].riW > 0)
+ {
+ Inverse (i);
+ eData[i].weight = 1;
+ }
+ else
+ {
+ eData[i].weight = 0;
+ SubEdge (i);
+ i--;
+ }
+ }
+ }
+ }
+ else if (directed == fill_nonZero)
+ {
+ if (invert)
+ {
+ for (int i = 0; i < numberOfEdges(); i++)
+ {
+ if (swdData[i].leW < 0 && swdData[i].riW == 0)
+ {
+ eData[i].weight = 1;
+ }
+ else if (swdData[i].leW > 0 && swdData[i].riW == 0)
+ {
+ eData[i].weight = 1;
+ }
+ else if (swdData[i].leW == 0 && swdData[i].riW < 0)
+ {
+ Inverse (i);
+ eData[i].weight = 1;
+ }
+ else if (swdData[i].leW == 0 && swdData[i].riW > 0)
+ {
+ Inverse (i);
+ eData[i].weight = 1;
+ }
+ else
+ {
+ eData[i].weight = 0;
+ SubEdge (i);
+ i--;
+ }
+ }
+ }
+ else
+ {
+ for (int i = 0; i < numberOfEdges(); i++)
+ {
+ if (swdData[i].leW > 0 && swdData[i].riW == 0)
+ {
+ eData[i].weight = 1;
+ }
+ else if (swdData[i].leW < 0 && swdData[i].riW == 0)
+ {
+ eData[i].weight = 1;
+ }
+ else if (swdData[i].leW == 0 && swdData[i].riW > 0)
+ {
+ Inverse (i);
+ eData[i].weight = 1;
+ }
+ else if (swdData[i].leW == 0 && swdData[i].riW < 0)
+ {
+ Inverse (i);
+ eData[i].weight = 1;
+ }
+ else
+ {
+ eData[i].weight = 0;
+ SubEdge (i);
+ i--;
+ }
+ }
+ }
+ }
+ else if (directed == fill_oddEven)
+ {
+ for (int i = 0; i < numberOfEdges(); i++)
+ {
+ swdData[i].leW %= 2;
+ swdData[i].riW %= 2;
+ if (swdData[i].leW < 0)
+ swdData[i].leW = -swdData[i].leW;
+ if (swdData[i].riW < 0)
+ swdData[i].riW = -swdData[i].riW;
+ if (swdData[i].leW > 0 && swdData[i].riW <= 0)
+ {
+ eData[i].weight = 1;
+ }
+ else if (swdData[i].leW <= 0 && swdData[i].riW > 0)
+ {
+ Inverse (i);
+ eData[i].weight = 1;
+ }
+ else
+ {
+ eData[i].weight = 0;
+ SubEdge (i);
+ i--;
+ }
+ }
+ } else if ( directed == fill_justDont ) {
+ for (int i=0;i<numberOfEdges();i++) {
+ if ( getEdge(i).st < 0 || getEdge(i).en < 0 ) {
+ SubEdge(i);
+ i--;
+ } else {
+ eData[i].weight = 0;
+ }
+ }
+ }
+
+ // Plot(200.0,200.0,2.0,400.0,400.0,true,true,true,true);
+
+ delete sTree;
+ sTree = nullptr;
+ delete sEvts;
+ sEvts = nullptr;
+
+ MakePointData (false);
+ MakeEdgeData (false);
+ MakeSweepSrcData (false);
+ MakeSweepDestData (false);
+ a->CleanupSweep ();
+ type = shape_polygon;
+ return 0;
+}
+
+// technically it's just a ConvertToShape() on 2 polygons at the same time, and different rules
+// for choosing the edges according to their winding numbers.
+// probably one of the biggest function i ever wrote.
+int
+Shape::Booleen (Shape * a, Shape * b, BooleanOp mod,int cutPathID)
+{
+ if (a == b || a == nullptr || b == nullptr)
+ return shape_input_err;
+ Reset (0, 0);
+ if (a->numberOfPoints() <= 1 || a->numberOfEdges() <= 1)
+ return 0;
+ if (b->numberOfPoints() <= 1 || b->numberOfEdges() <= 1)
+ return 0;
+ if ( mod == bool_op_cut ) {
+ } else if ( mod == bool_op_slice ) {
+ } else {
+ if (a->type != shape_polygon)
+ return shape_input_err;
+ if (b->type != shape_polygon)
+ return shape_input_err;
+ }
+
+ a->ResetSweep ();
+ b->ResetSweep ();
+
+ if (sTree == nullptr) {
+ sTree = new SweepTreeList(a->numberOfEdges() + b->numberOfEdges());
+ }
+ if (sEvts == nullptr) {
+ sEvts = new SweepEventQueue(a->numberOfEdges() + b->numberOfEdges());
+ }
+
+ MakePointData (true);
+ MakeEdgeData (true);
+ MakeSweepSrcData (true);
+ MakeSweepDestData (true);
+ if (a->hasBackData () && b->hasBackData ())
+ {
+ MakeBackData (true);
+ }
+ else
+ {
+ MakeBackData (false);
+ }
+
+ a->initialisePointData();
+ b->initialisePointData();
+
+ a->initialiseEdgeData();
+ b->initialiseEdgeData();
+
+ a->SortPointsRounded ();
+ b->SortPointsRounded ();
+
+ chgts.clear();
+
+ double lastChange =
+ (a->pData[0].rx[1] <
+ b->pData[0].rx[1]) ? a->pData[0].rx[1] - 1.0 : b->pData[0].rx[1] - 1.0;
+ int lastChgtPt = 0;
+ int edgeHead = -1;
+ Shape *shapeHead = nullptr;
+
+ clearIncidenceData();
+
+ int curAPt = 0;
+ int curBPt = 0;
+
+ while (curAPt < a->numberOfPoints() || curBPt < b->numberOfPoints() || sEvts->size() > 0)
+ {
+/* for (int i=0;i<sEvts.nbEvt;i++) {
+ printf("%f %f %i %i\n",sEvts.events[i].posx,sEvts.events[i].posy,sEvts.events[i].leftSweep->bord,sEvts.events[i].rightSweep->bord); // localizing ok
+ }
+ // cout << endl;
+ if ( sTree.racine ) {
+ SweepTree* ct=static_cast <SweepTree*> (sTree.racine->Leftmost());
+ while ( ct ) {
+ printf("%i %i [%i\n",ct->bord,ct->startPoint,(ct->src==a)?1:0);
+ ct=static_cast <SweepTree*> (ct->elem[RIGHT]);
+ }
+ }
+ printf("\n");*/
+
+ Geom::Point ptX;
+ double ptL, ptR;
+ SweepTree *intersL = nullptr;
+ SweepTree *intersR = nullptr;
+ int nPt = -1;
+ Shape *ptSh = nullptr;
+ bool isIntersection = false;
+
+ if (sEvts->peek(intersL, intersR, ptX, ptL, ptR))
+ {
+ if (curAPt < a->numberOfPoints())
+ {
+ if (curBPt < b->numberOfPoints())
+ {
+ if (a->pData[curAPt].rx[1] < b->pData[curBPt].rx[1]
+ || (a->pData[curAPt].rx[1] == b->pData[curBPt].rx[1]
+ && a->pData[curAPt].rx[0] < b->pData[curBPt].rx[0]))
+ {
+ if (a->pData[curAPt].pending > 0
+ || (a->pData[curAPt].rx[1] > ptX[1]
+ || (a->pData[curAPt].rx[1] == ptX[1]
+ && a->pData[curAPt].rx[0] > ptX[0])))
+ {
+ /* FIXME: could be pop? */
+ sEvts->extract(intersL, intersR, ptX, ptL, ptR);
+ isIntersection = true;
+ }
+ else
+ {
+ nPt = curAPt++;
+ ptSh = a;
+ ptX = ptSh->pData[nPt].rx;
+ isIntersection = false;
+ }
+ }
+ else
+ {
+ if (b->pData[curBPt].pending > 0
+ || (b->pData[curBPt].rx[1] > ptX[1]
+ || (b->pData[curBPt].rx[1] == ptX[1]
+ && b->pData[curBPt].rx[0] > ptX[0])))
+ {
+ /* FIXME: could be pop? */
+ sEvts->extract(intersL, intersR, ptX, ptL, ptR);
+ isIntersection = true;
+ }
+ else
+ {
+ nPt = curBPt++;
+ ptSh = b;
+ ptX = ptSh->pData[nPt].rx;
+ isIntersection = false;
+ }
+ }
+ }
+ else
+ {
+ if (a->pData[curAPt].pending > 0
+ || (a->pData[curAPt].rx[1] > ptX[1]
+ || (a->pData[curAPt].rx[1] == ptX[1]
+ && a->pData[curAPt].rx[0] > ptX[0])))
+ {
+ /* FIXME: could be pop? */
+ sEvts->extract(intersL, intersR, ptX, ptL, ptR);
+ isIntersection = true;
+ }
+ else
+ {
+ nPt = curAPt++;
+ ptSh = a;
+ ptX = ptSh->pData[nPt].rx;
+ isIntersection = false;
+ }
+ }
+ }
+ else
+ {
+ if (b->pData[curBPt].pending > 0
+ || (b->pData[curBPt].rx[1] > ptX[1]
+ || (b->pData[curBPt].rx[1] == ptX[1]
+ && b->pData[curBPt].rx[0] > ptX[0])))
+ {
+ /* FIXME: could be pop? */
+ sEvts->extract(intersL, intersR, ptX, ptL, ptR);
+ isIntersection = true;
+ }
+ else
+ {
+ nPt = curBPt++;
+ ptSh = b;
+ ptX = ptSh->pData[nPt].rx;
+ isIntersection = false;
+ }
+ }
+ }
+ else
+ {
+ if (curAPt < a->numberOfPoints())
+ {
+ if (curBPt < b->numberOfPoints())
+ {
+ if (a->pData[curAPt].rx[1] < b->pData[curBPt].rx[1]
+ || (a->pData[curAPt].rx[1] == b->pData[curBPt].rx[1]
+ && a->pData[curAPt].rx[0] < b->pData[curBPt].rx[0]))
+ {
+ nPt = curAPt++;
+ ptSh = a;
+ }
+ else
+ {
+ nPt = curBPt++;
+ ptSh = b;
+ }
+ }
+ else
+ {
+ nPt = curAPt++;
+ ptSh = a;
+ }
+ }
+ else
+ {
+ nPt = curBPt++;
+ ptSh = b;
+ }
+ ptX = ptSh->pData[nPt].rx;
+ isIntersection = false;
+ }
+
+ if (isIntersection == false)
+ {
+ if (ptSh->getPoint(nPt).dI == 0 && ptSh->getPoint(nPt).dO == 0)
+ continue;
+ }
+
+ Geom::Point rPtX;
+ rPtX[0]= Round (ptX[0]);
+ rPtX[1]= Round (ptX[1]);
+ int lastPointNo = AddPoint (rPtX);
+ pData[lastPointNo].rx = rPtX;
+
+ if (rPtX[1] > lastChange)
+ {
+ int lastI = AssemblePoints (lastChgtPt, lastPointNo);
+
+
+ Shape *curSh = shapeHead;
+ int curBo = edgeHead;
+ while (curSh)
+ {
+ curSh->swsData[curBo].leftRnd =
+ pData[curSh->swsData[curBo].leftRnd].newInd;
+ curSh->swsData[curBo].rightRnd =
+ pData[curSh->swsData[curBo].rightRnd].newInd;
+
+ Shape *neSh = curSh->swsData[curBo].nextSh;
+ curBo = curSh->swsData[curBo].nextBo;
+ curSh = neSh;
+ }
+
+ for (auto & chgt : chgts)
+ {
+ chgt.ptNo = pData[chgt.ptNo].newInd;
+ if (chgt.type == 0)
+ {
+ if (chgt.src->getEdge(chgt.bord).st <
+ chgt.src->getEdge(chgt.bord).en)
+ {
+ chgt.src->swsData[chgt.bord].stPt =
+ chgt.ptNo;
+ }
+ else
+ {
+ chgt.src->swsData[chgt.bord].enPt =
+ chgt.ptNo;
+ }
+ }
+ else if (chgt.type == 1)
+ {
+ if (chgt.src->getEdge(chgt.bord).st >
+ chgt.src->getEdge(chgt.bord).en)
+ {
+ chgt.src->swsData[chgt.bord].stPt =
+ chgt.ptNo;
+ }
+ else
+ {
+ chgt.src->swsData[chgt.bord].enPt =
+ chgt.ptNo;
+ }
+ }
+ }
+
+ CheckAdjacencies (lastI, lastChgtPt, shapeHead, edgeHead);
+
+ CheckEdges (lastI, lastChgtPt, a, b, mod);
+
+ for (int i = lastChgtPt; i < lastI; i++)
+ {
+ if (pData[i].askForWindingS)
+ {
+ Shape *windS = pData[i].askForWindingS;
+ int windB = pData[i].askForWindingB;
+ pData[i].nextLinkedPoint =
+ windS->swsData[windB].firstLinkedPoint;
+ windS->swsData[windB].firstLinkedPoint = i;
+ }
+ }
+
+ if (lastI < lastPointNo)
+ {
+ _pts[lastI] = getPoint(lastPointNo);
+ pData[lastI] = pData[lastPointNo];
+ }
+ lastPointNo = lastI;
+ _pts.resize(lastI + 1);
+
+ lastChgtPt = lastPointNo;
+ lastChange = rPtX[1];
+ chgts.clear();
+ edgeHead = -1;
+ shapeHead = nullptr;
+ }
+
+
+ if (isIntersection)
+ {
+ // les 2 events de part et d'autre de l'intersection
+ // (celui de l'intersection a deja ete depile)
+ intersL->RemoveEvent (*sEvts, LEFT);
+ intersR->RemoveEvent (*sEvts, RIGHT);
+
+ AddChgt (lastPointNo, lastChgtPt, shapeHead, edgeHead, INTERSECTION,
+ intersL->src, intersL->bord, intersR->src, intersR->bord);
+
+ intersL->SwapWithRight (*sTree, *sEvts);
+
+ TesteIntersection (intersL, LEFT, true);
+ TesteIntersection (intersR, RIGHT, true);
+ }
+ else
+ {
+ int cb;
+
+ int nbUp = 0, nbDn = 0;
+ int upNo = -1, dnNo = -1;
+ cb = ptSh->getPoint(nPt).incidentEdge[FIRST];
+ while (cb >= 0 && cb < ptSh->numberOfEdges())
+ {
+ if ((ptSh->getEdge(cb).st < ptSh->getEdge(cb).en
+ && nPt == ptSh->getEdge(cb).en)
+ || (ptSh->getEdge(cb).st > ptSh->getEdge(cb).en
+ && nPt == ptSh->getEdge(cb).st))
+ {
+ upNo = cb;
+ nbUp++;
+ }
+ if ((ptSh->getEdge(cb).st > ptSh->getEdge(cb).en
+ && nPt == ptSh->getEdge(cb).en)
+ || (ptSh->getEdge(cb).st < ptSh->getEdge(cb).en
+ && nPt == ptSh->getEdge(cb).st))
+ {
+ dnNo = cb;
+ nbDn++;
+ }
+ cb = ptSh->NextAt (nPt, cb);
+ }
+
+ if (nbDn <= 0)
+ {
+ upNo = -1;
+ }
+ if (upNo >= 0 && (SweepTree *) ptSh->swsData[upNo].misc == nullptr)
+ {
+ upNo = -1;
+ }
+
+// upNo=-1;
+
+ bool doWinding = true;
+
+ if (nbUp > 0)
+ {
+ cb = ptSh->getPoint(nPt).incidentEdge[FIRST];
+ while (cb >= 0 && cb < ptSh->numberOfEdges())
+ {
+ if ((ptSh->getEdge(cb).st < ptSh->getEdge(cb).en
+ && nPt == ptSh->getEdge(cb).en)
+ || (ptSh->getEdge(cb).st > ptSh->getEdge(cb).en
+ && nPt == ptSh->getEdge(cb).st))
+ {
+ if (cb != upNo)
+ {
+ SweepTree *node =
+ (SweepTree *) ptSh->swsData[cb].misc;
+ if (node == nullptr)
+ {
+ }
+ else
+ {
+ AddChgt (lastPointNo, lastChgtPt, shapeHead,
+ edgeHead, EDGE_REMOVED, node->src, node->bord,
+ nullptr, -1);
+ ptSh->swsData[cb].misc = nullptr;
+
+ int onLeftB = -1, onRightB = -1;
+ Shape *onLeftS = nullptr;
+ Shape *onRightS = nullptr;
+ if (node->elem[LEFT])
+ {
+ onLeftB =
+ (static_cast <
+ SweepTree * >(node->elem[LEFT]))->bord;
+ onLeftS =
+ (static_cast <
+ SweepTree * >(node->elem[LEFT]))->src;
+ }
+ if (node->elem[RIGHT])
+ {
+ onRightB =
+ (static_cast <
+ SweepTree * >(node->elem[RIGHT]))->bord;
+ onRightS =
+ (static_cast <
+ SweepTree * >(node->elem[RIGHT]))->src;
+ }
+
+ node->Remove (*sTree, *sEvts, true);
+ if (onLeftS && onRightS)
+ {
+ SweepTree *onLeft =
+ (SweepTree *) onLeftS->swsData[onLeftB].
+ misc;
+// SweepTree* onRight=(SweepTree*)onRightS->swsData[onRightB].misc;
+ if (onLeftS == ptSh
+ && (onLeftS->getEdge(onLeftB).en == nPt
+ || onLeftS->getEdge(onLeftB).st ==
+ nPt))
+ {
+ }
+ else
+ {
+ if (onRightS == ptSh
+ && (onRightS->getEdge(onRightB).en ==
+ nPt
+ || onRightS->getEdge(onRightB).
+ st == nPt))
+ {
+ }
+ else
+ {
+ TesteIntersection (onLeft, RIGHT, true);
+ }
+ }
+ }
+ }
+ }
+ }
+ cb = ptSh->NextAt (nPt, cb);
+ }
+ }
+
+ // traitement du "upNo devient dnNo"
+ SweepTree *insertionNode = nullptr;
+ if (dnNo >= 0)
+ {
+ if (upNo >= 0)
+ {
+ SweepTree *node = (SweepTree *) ptSh->swsData[upNo].misc;
+
+ AddChgt (lastPointNo, lastChgtPt, shapeHead, edgeHead, EDGE_REMOVED,
+ node->src, node->bord, nullptr, -1);
+
+ ptSh->swsData[upNo].misc = nullptr;
+
+ node->RemoveEvents (*sEvts);
+ node->ConvertTo (ptSh, dnNo, 1, lastPointNo);
+ ptSh->swsData[dnNo].misc = node;
+ TesteIntersection (node, RIGHT, true);
+ TesteIntersection (node, LEFT, true);
+ insertionNode = node;
+
+ ptSh->swsData[dnNo].curPoint = lastPointNo;
+
+ AddChgt (lastPointNo, lastChgtPt, shapeHead, edgeHead, EDGE_INSERTED,
+ node->src, node->bord, nullptr, -1);
+ }
+ else
+ {
+ SweepTree *node = sTree->add(ptSh, dnNo, 1, lastPointNo, this);
+ ptSh->swsData[dnNo].misc = node;
+ node->Insert (*sTree, *sEvts, this, lastPointNo, true);
+
+ if (doWinding)
+ {
+ SweepTree *myLeft =
+ static_cast < SweepTree * >(node->elem[LEFT]);
+ if (myLeft)
+ {
+ pData[lastPointNo].askForWindingS = myLeft->src;
+ pData[lastPointNo].askForWindingB = myLeft->bord;
+ }
+ else
+ {
+ pData[lastPointNo].askForWindingB = -1;
+ }
+ doWinding = false;
+ }
+
+ TesteIntersection (node, RIGHT, true);
+ TesteIntersection (node, LEFT, true);
+ insertionNode = node;
+
+ ptSh->swsData[dnNo].curPoint = lastPointNo;
+
+ AddChgt (lastPointNo, lastChgtPt, shapeHead, edgeHead, EDGE_INSERTED,
+ node->src, node->bord, nullptr, -1);
+ }
+ }
+
+ if (nbDn > 1)
+ { // si nbDn == 1 , alors dnNo a deja ete traite
+ cb = ptSh->getPoint(nPt).incidentEdge[FIRST];
+ while (cb >= 0 && cb < ptSh->numberOfEdges())
+ {
+ if ((ptSh->getEdge(cb).st > ptSh->getEdge(cb).en
+ && nPt == ptSh->getEdge(cb).en)
+ || (ptSh->getEdge(cb).st < ptSh->getEdge(cb).en
+ && nPt == ptSh->getEdge(cb).st))
+ {
+ if (cb != dnNo)
+ {
+ SweepTree *node = sTree->add(ptSh, cb, 1, lastPointNo, this);
+ ptSh->swsData[cb].misc = node;
+// node->Insert(sTree,*sEvts,this,lastPointNo,true);
+ node->InsertAt (*sTree, *sEvts, this, insertionNode,
+ nPt, true);
+
+ if (doWinding)
+ {
+ SweepTree *myLeft =
+ static_cast < SweepTree * >(node->elem[LEFT]);
+ if (myLeft)
+ {
+ pData[lastPointNo].askForWindingS =
+ myLeft->src;
+ pData[lastPointNo].askForWindingB =
+ myLeft->bord;
+ }
+ else
+ {
+ pData[lastPointNo].askForWindingB = -1;
+ }
+ doWinding = false;
+ }
+
+ TesteIntersection (node, RIGHT, true);
+ TesteIntersection (node, LEFT, true);
+
+ ptSh->swsData[cb].curPoint = lastPointNo;
+
+ AddChgt (lastPointNo, lastChgtPt, shapeHead,
+ edgeHead, EDGE_INSERTED, node->src, node->bord, nullptr,
+ -1);
+ }
+ }
+ cb = ptSh->NextAt (nPt, cb);
+ }
+ }
+ }
+ }
+ {
+ int lastI = AssemblePoints (lastChgtPt, numberOfPoints());
+
+
+ Shape *curSh = shapeHead;
+ int curBo = edgeHead;
+ while (curSh)
+ {
+ curSh->swsData[curBo].leftRnd =
+ pData[curSh->swsData[curBo].leftRnd].newInd;
+ curSh->swsData[curBo].rightRnd =
+ pData[curSh->swsData[curBo].rightRnd].newInd;
+
+ Shape *neSh = curSh->swsData[curBo].nextSh;
+ curBo = curSh->swsData[curBo].nextBo;
+ curSh = neSh;
+ }
+
+ /* FIXME: this kind of code seems to appear frequently */
+ for (auto & chgt : chgts)
+ {
+ chgt.ptNo = pData[chgt.ptNo].newInd;
+ if (chgt.type == 0)
+ {
+ if (chgt.src->getEdge(chgt.bord).st <
+ chgt.src->getEdge(chgt.bord).en)
+ {
+ chgt.src->swsData[chgt.bord].stPt = chgt.ptNo;
+ }
+ else
+ {
+ chgt.src->swsData[chgt.bord].enPt = chgt.ptNo;
+ }
+ }
+ else if (chgt.type == 1)
+ {
+ if (chgt.src->getEdge(chgt.bord).st >
+ chgt.src->getEdge(chgt.bord).en)
+ {
+ chgt.src->swsData[chgt.bord].stPt = chgt.ptNo;
+ }
+ else
+ {
+ chgt.src->swsData[chgt.bord].enPt = chgt.ptNo;
+ }
+ }
+ }
+
+ CheckAdjacencies (lastI, lastChgtPt, shapeHead, edgeHead);
+
+ CheckEdges (lastI, lastChgtPt, a, b, mod);
+
+ for (int i = lastChgtPt; i < lastI; i++)
+ {
+ if (pData[i].askForWindingS)
+ {
+ Shape *windS = pData[i].askForWindingS;
+ int windB = pData[i].askForWindingB;
+ pData[i].nextLinkedPoint = windS->swsData[windB].firstLinkedPoint;
+ windS->swsData[windB].firstLinkedPoint = i;
+ }
+ }
+
+ _pts.resize(lastI);
+
+ edgeHead = -1;
+ shapeHead = nullptr;
+ }
+
+ chgts.clear();
+ clearIncidenceData();
+
+// Plot(190,70,6,400,400,true,false,true,true);
+
+ if ( mod == bool_op_cut ) {
+ AssembleAretes (fill_justDont);
+ // dupliquer les aretes de la coupure
+ int i=numberOfEdges()-1;
+ for (;i>=0;i--) {
+ if ( ebData[i].pathID == cutPathID ) {
+ // on duplique
+ int nEd=AddEdge(getEdge(i).en,getEdge(i).st);
+ ebData[nEd].pathID=cutPathID;
+ ebData[nEd].pieceID=ebData[i].pieceID;
+ ebData[nEd].tSt=ebData[i].tEn;
+ ebData[nEd].tEn=ebData[i].tSt;
+ eData[nEd].weight=eData[i].weight;
+ // lui donner les firstlinkedpoitn si besoin
+ if ( getEdge(i).en >= getEdge(i).st ) {
+ int cp = swsData[i].firstLinkedPoint;
+ while (cp >= 0) {
+ pData[cp].askForWindingB = nEd;
+ cp = pData[cp].nextLinkedPoint;
+ }
+ swsData[nEd].firstLinkedPoint = swsData[i].firstLinkedPoint;
+ swsData[i].firstLinkedPoint=-1;
+ }
+ }
+ }
+ } else if ( mod == bool_op_slice ) {
+ } else {
+ AssembleAretes ();
+ }
+
+ for (int i = 0; i < numberOfPoints(); i++)
+ {
+ _pts[i].oldDegree = getPoint(i).totalDegree();
+ }
+
+ _need_edges_sorting = true;
+ if ( mod == bool_op_slice ) {
+ } else {
+ GetWindings (a, b, mod, false);
+ }
+// Plot(190,70,6,400,400,true,true,true,true);
+
+ if (mod == bool_op_symdiff)
+ {
+ for (int i = 0; i < numberOfEdges(); i++)
+ {
+ if (swdData[i].leW < 0)
+ swdData[i].leW = -swdData[i].leW;
+ if (swdData[i].riW < 0)
+ swdData[i].riW = -swdData[i].riW;
+
+ if (swdData[i].leW > 0 && swdData[i].riW <= 0)
+ {
+ eData[i].weight = 1;
+ }
+ else if (swdData[i].leW <= 0 && swdData[i].riW > 0)
+ {
+ Inverse (i);
+ eData[i].weight = 1;
+ }
+ else
+ {
+ eData[i].weight = 0;
+ SubEdge (i);
+ i--;
+ }
+ }
+ }
+ else if (mod == bool_op_union || mod == bool_op_diff)
+ {
+ for (int i = 0; i < numberOfEdges(); i++)
+ {
+ if (swdData[i].leW > 0 && swdData[i].riW <= 0)
+ {
+ eData[i].weight = 1;
+ }
+ else if (swdData[i].leW <= 0 && swdData[i].riW > 0)
+ {
+ Inverse (i);
+ eData[i].weight = 1;
+ }
+ else
+ {
+ eData[i].weight = 0;
+ SubEdge (i);
+ i--;
+ }
+ }
+ }
+ else if (mod == bool_op_inters)
+ {
+ for (int i = 0; i < numberOfEdges(); i++)
+ {
+ if (swdData[i].leW > 1 && swdData[i].riW <= 1)
+ {
+ eData[i].weight = 1;
+ }
+ else if (swdData[i].leW <= 1 && swdData[i].riW > 1)
+ {
+ Inverse (i);
+ eData[i].weight = 1;
+ }
+ else
+ {
+ eData[i].weight = 0;
+ SubEdge (i);
+ i--;
+ }
+ }
+ } else if ( mod == bool_op_cut ) {
+ // inverser les aretes de la coupe au besoin
+ for (int i=0;i<numberOfEdges();i++) {
+ if ( getEdge(i).st < 0 || getEdge(i).en < 0 ) {
+ if ( i < numberOfEdges()-1 ) {
+ // decaler les askForWinding
+ int cp = swsData[numberOfEdges()-1].firstLinkedPoint;
+ while (cp >= 0) {
+ pData[cp].askForWindingB = i;
+ cp = pData[cp].nextLinkedPoint;
+ }
+ }
+ SwapEdges(i,numberOfEdges()-1);
+ SubEdge(numberOfEdges()-1);
+// SubEdge(i);
+ i--;
+ } else if ( ebData[i].pathID == cutPathID ) {
+ swdData[i].leW=swdData[i].leW%2;
+ swdData[i].riW=swdData[i].riW%2;
+ if ( swdData[i].leW < swdData[i].riW ) {
+ Inverse(i);
+ }
+ }
+ }
+ } else if ( mod == bool_op_slice ) {
+ // supprimer les aretes de la coupe
+ int i=numberOfEdges()-1;
+ for (;i>=0;i--) {
+ if ( ebData[i].pathID == cutPathID || getEdge(i).st < 0 || getEdge(i).en < 0 ) {
+ SubEdge(i);
+ }
+ }
+ }
+ else
+ {
+ for (int i = 0; i < numberOfEdges(); i++)
+ {
+ if (swdData[i].leW > 0 && swdData[i].riW <= 0)
+ {
+ eData[i].weight = 1;
+ }
+ else if (swdData[i].leW <= 0 && swdData[i].riW > 0)
+ {
+ Inverse (i);
+ eData[i].weight = 1;
+ }
+ else
+ {
+ eData[i].weight = 0;
+ SubEdge (i);
+ i--;
+ }
+ }
+ }
+
+ delete sTree;
+ sTree = nullptr;
+ delete sEvts;
+ sEvts = nullptr;
+
+ if ( mod == bool_op_cut ) {
+ // on garde le askForWinding
+ } else {
+ MakePointData (false);
+ }
+ MakeEdgeData (false);
+ MakeSweepSrcData (false);
+ MakeSweepDestData (false);
+ a->CleanupSweep ();
+ b->CleanupSweep ();
+
+ if (directedEulerian(this) == false)
+ {
+// printf( "pas euclidian2");
+ _pts.clear();
+ _aretes.clear();
+ return shape_euler_err;
+ }
+ type = shape_polygon;
+ return 0;
+}
+
+// frontend to the TesteIntersection() below
+void Shape::TesteIntersection(SweepTree *t, Side s, bool onlyDiff)
+{
+ // get the element that is to the side s of node t
+ SweepTree *tt = static_cast<SweepTree*>(t->elem[s]);
+ if (tt == nullptr) {
+ return;
+ }
+
+ // set left right properly, a is left, b is right
+ SweepTree *a = (s == LEFT) ? tt : t;
+ SweepTree *b = (s == LEFT) ? t : tt;
+
+ // call the actual intersection checking function and if an intersection
+ // is detected, add it as an event
+ Geom::Point atx;
+ double atl;
+ double atr;
+ if (TesteIntersection(a, b, atx, atl, atr, onlyDiff)) {
+ sEvts->add(a, b, atx, atl, atr);
+ }
+}
+
+// a crucial piece of code: computing intersections between segments
+bool
+Shape::TesteIntersection (SweepTree * iL, SweepTree * iR, Geom::Point &atx, double &atL, double &atR, bool onlyDiff)
+{
+ // get the left edge's start and end point
+ int lSt = iL->src->getEdge(iL->bord).st, lEn = iL->src->getEdge(iL->bord).en;
+ // get the right edge's start and end point
+ int rSt = iR->src->getEdge(iR->bord).st, rEn = iR->src->getEdge(iR->bord).en;
+ // get both edge vectors
+ Geom::Point ldir, rdir;
+ ldir = iL->src->eData[iL->bord].rdx;
+ rdir = iR->src->eData[iR->bord].rdx;
+ // first, a round of checks to quickly dismiss edge which obviously dont intersect,
+ // such as having disjoint bounding boxes
+
+ // invert the edge vector and swap the endpoints if an edge is bottom to top
+ // or horizontal and right to left
+ if (lSt < lEn)
+ {
+ }
+ else
+ {
+ std::swap(lSt, lEn);
+ ldir = -ldir;
+ }
+ if (rSt < rEn)
+ {
+ }
+ else
+ {
+ std::swap(rSt, rEn);
+ rdir = -rdir;
+ }
+
+ // these blocks check if the bounding boxes of the two don't overlap, if they
+ // don't overlap, we can just return false since non-overlapping bounding boxes
+ // indicate they won't intersect
+ if (iL->src->pData[lSt].rx[0] < iL->src->pData[lEn].rx[0])
+ {
+ if (iR->src->pData[rSt].rx[0] < iR->src->pData[rEn].rx[0])
+ {
+ if (iL->src->pData[lSt].rx[0] > iR->src->pData[rEn].rx[0])
+ return false;
+ if (iL->src->pData[lEn].rx[0] < iR->src->pData[rSt].rx[0])
+ return false;
+ }
+ else
+ {
+ if (iL->src->pData[lSt].rx[0] > iR->src->pData[rSt].rx[0])
+ return false;
+ if (iL->src->pData[lEn].rx[0] < iR->src->pData[rEn].rx[0])
+ return false;
+ }
+ }
+ else
+ {
+ if (iR->src->pData[rSt].rx[0] < iR->src->pData[rEn].rx[0])
+ {
+ if (iL->src->pData[lEn].rx[0] > iR->src->pData[rEn].rx[0])
+ return false;
+ if (iL->src->pData[lSt].rx[0] < iR->src->pData[rSt].rx[0])
+ return false;
+ }
+ else
+ {
+ if (iL->src->pData[lEn].rx[0] > iR->src->pData[rSt].rx[0])
+ return false;
+ if (iL->src->pData[lSt].rx[0] < iR->src->pData[rEn].rx[0])
+ return false;
+ }
+ }
+
+ // see the second image in the header docs of this function to visualize
+ // this cross product
+ double ang = cross (ldir, rdir);
+ // ang*=iL->src->eData[iL->bord].isqlength;
+ // ang*=iR->src->eData[iR->bord].isqlength;
+ if (ang <= 0) return false; // edges in opposite directions: <-left ... right ->
+ // they can't intersect
+
+ // d'abord tester les bords qui partent d'un meme point
+ // if they come from same shape and they share the same start point
+ if (iL->src == iR->src && lSt == rSt)
+ {
+ // if they share the end point too, it's a doublon doesn't count as intersection
+ if (iL->src == iR->src && lEn == rEn)
+ return false; // c'est juste un doublon
+ // if we are here, it means they share the start point only and that counts as an interesection
+ // intersection point is the starting point and times are all set to -1
+ atx = iL->src->pData[lSt].rx;
+ atR = atL = -1;
+ return true; // l'ordre est mauvais
+ }
+ // if they only share the end points, doesn't count as intersection (no idea why)
+ // in my opinion, both endpoints shouldn't count for intersection
+ if (iL->src == iR->src && lEn == rEn)
+ return false; // rien a faire=ils vont terminer au meme endroit
+
+ // tester si on est dans une intersection multiple
+
+ // I'm not sure what onlyDiff does but it seems it stands for "only different", which means, the intersections
+ // will only be detected if the two edges are coming from different shapes, if they come from the same shape,
+ // don't do any checks, we are not interested. but this if statement should be somewhere above in the code
+ // not here, shouldn't it? Why bother doing the bounding box checks?
+ if (onlyDiff && iL->src == iR->src)
+ return false;
+
+ // on reprend les vrais points
+ // get the start end points again, since we might have swapped them above
+ lSt = iL->src->getEdge(iL->bord).st;
+ lEn = iL->src->getEdge(iL->bord).en;
+ rSt = iR->src->getEdge(iR->bord).st;
+ rEn = iR->src->getEdge(iR->bord).en;
+
+ // compute intersection (if there is one)
+ // Boissonat anr Preparata said in one paper that double precision floats were sufficient for get single precision
+ // coordinates for the intersection, if the endpoints are single precision. i hope they're right...
+ // so till here, lSt, lEn, rSt, rEn have been reset, but note that ldir and rdir are the same
+ {
+ Geom::Point sDiff, eDiff;
+ double slDot, elDot;
+ double srDot, erDot;
+ // a vector from the start point of right edge to the start point of left edge
+ sDiff = iL->src->pData[lSt].rx - iR->src->pData[rSt].rx;
+ // a vector from the start point of right edge to the end point of left edge
+ eDiff = iL->src->pData[lEn].rx - iR->src->pData[rSt].rx;
+ srDot = cross(rdir, sDiff);
+ erDot = cross(rdir, eDiff);
+ // a vector from the start point of left edge to the start point of right edge
+ sDiff = iR->src->pData[rSt].rx - iL->src->pData[lSt].rx;
+ // a vector from the start point of left edge to the end point of right edge
+ eDiff = iR->src->pData[rEn].rx - iL->src->pData[lSt].rx;
+ slDot = cross(ldir, sDiff);
+ elDot = cross(ldir, eDiff);
+ // these cross products above are shown in the third picture in the header docs of this function.
+ // The only thing that matters to us at the moment about these cross products is their sign
+ // not their value, the value comes in later. I've labelled the angle arcs with the name of the
+ // cross product so that you can immediately visualize the sign that the cross product will take
+ // take your right hand, index finger to first vector, middle finger to second vector, if thumb
+ // is into the page, cross is positive, else it's negative.
+
+ // basically these cross products give us a sense of where the endpoints of other vector is with
+ // respect to a particular vector. If both are on the opposite sides, it indicates that an intersection
+ // will happen. Of course you can have the edge far away and still have endpoints on opposite side,
+ // they won't intersect but those cases have already been ruled out above
+
+ // if both endpoints of left edge are on one side (the same side doesn't matter which one) of the right edge
+ if ((srDot >= 0 && erDot >= 0) || (srDot <= 0 && erDot <= 0))
+ {
+ // you might think okay if both endpoints of left edge are on same side of right edge, then they can't intersect
+ // right? no, they still can. An endpoint of the left edge can fall on the right edge and in that case
+ // there is an intersection
+ // the start point of the left edge is on the right edge
+ if (srDot == 0)
+ {
+ // this condition here is quite weird, due to it, some intersections won't get detected while others might, I
+ // don't really see why it has been done this way. See the fourth figure in the header docs of this function and
+ // you'll see both cases. One where the condition lSt < lEn is valid and an intersection is detected and another
+ // one where it isn't. In fact as I was testing this out, I realized the purpose of CheckAdjacencies. Apparently
+ // when a point of intersection is missed by this function, the edge still gets split up at the intersection point
+ // thanks to CheckAdjacencies. You can see this for yourself by taking a 1000 px by 1000 px canvas and the following
+ // SVG path: M 500,200 L 500,800 L 200,800 L 500,500 L 200,200 Z
+ // Try Path > Union with and without the CheckAdjacencies call and you'll see the difference in the resultant path.
+ // So I was trying to find out a case where this if statement lSt < lEn would be true and an intersection would be
+ // returned, I couldn't succeed at this. You can get lSt < lEn to be true, but something else above in the code will
+ // cause an early return, most likely the ang <= 0 condition. So in order words, I couldn't get the code below to
+ // run, ever.
+ if (lSt < lEn)
+ {
+ atx = iL->src->pData[lSt].rx;
+ atL = 0;
+ atR = slDot / (slDot - elDot);
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+ else if (erDot == 0)
+ {
+ if (lSt > lEn)
+ {
+ atx = iL->src->pData[lEn].rx;
+ atL = 1;
+ atR = slDot / (slDot - elDot);
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+ // This code doesn't make sense either, I couldn't get it to trigger
+ // TODO: Try again or just prove that it's impossible to reach here
+ if (srDot > 0 && erDot > 0)
+ {
+ if (rEn < rSt)
+ {
+ if (srDot < erDot)
+ {
+ if (lSt < lEn)
+ {
+ atx = iL->src->pData[lSt].rx;
+ atL = 0;
+ atR = slDot / (slDot - elDot);
+ return true;
+ }
+ }
+ else
+ {
+ if (lEn < lSt)
+ {
+ atx = iL->src->pData[lEn].rx;
+ atL = 1;
+ atR = slDot / (slDot - elDot);
+ return true;
+ }
+ }
+ }
+ }
+ if (srDot < 0 && erDot < 0)
+ {
+ if (rEn > rSt)
+ {
+ if (srDot > erDot)
+ {
+ if (lSt < lEn)
+ {
+ atx = iL->src->pData[lSt].rx;
+ atL = 0;
+ atR = slDot / (slDot - elDot);
+ return true;
+ }
+ }
+ else
+ {
+ if (lEn < lSt)
+ {
+ atx = iL->src->pData[lEn].rx;
+ atL = 1;
+ atR = slDot / (slDot - elDot);
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+ // if both endpoints of the right edge are on the same side of left edge
+ if ((slDot >= 0 && elDot >= 0) || (slDot <= 0 && elDot <= 0))
+ {
+ if (slDot == 0)
+ {
+ if (rSt < rEn)
+ {
+ atx = iR->src->pData[rSt].rx;
+ atR = 0;
+ atL = srDot / (srDot - erDot);
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+ else if (elDot == 0)
+ {
+ if (rSt > rEn)
+ {
+ atx = iR->src->pData[rEn].rx;
+ atR = 1;
+ atL = srDot / (srDot - erDot);
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+ if (slDot > 0 && elDot > 0)
+ {
+ if (lEn > lSt)
+ {
+ if (slDot < elDot)
+ {
+ if (rSt < rEn)
+ {
+ atx = iR->src->pData[rSt].rx;
+ atR = 0;
+ atL = srDot / (srDot - erDot);
+ return true;
+ }
+ }
+ else
+ {
+ if (rEn < rSt)
+ {
+ atx = iR->src->pData[rEn].rx;
+ atR = 1;
+ atL = srDot / (srDot - erDot);
+ return true;
+ }
+ }
+ }
+ }
+ if (slDot < 0 && elDot < 0)
+ {
+ if (lEn < lSt)
+ {
+ if (slDot > elDot)
+ {
+ if (rSt < rEn)
+ {
+ atx = iR->src->pData[rSt].rx;
+ atR = 0;
+ atL = srDot / (srDot - erDot);
+ return true;
+ }
+ }
+ else
+ {
+ if (rEn < rSt)
+ {
+ atx = iR->src->pData[rEn].rx;
+ atR = 1;
+ atL = srDot / (srDot - erDot);
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ /* double slb=slDot-elDot,srb=srDot-erDot;
+ if ( slb < 0 ) slb=-slb;
+ if ( srb < 0 ) srb=-srb;*/
+ // We use different formulas depending on whose sin is greater
+
+ // These formulas would look really weird to you, but let's pick the first one
+ // and I'll do some maths that you can see in the header docs to elaborate what's
+ // happening
+ if (iL->src->eData[iL->bord].siEd > iR->src->eData[iR->bord].siEd)
+ {
+ atx =
+ (slDot * iR->src->pData[rEn].rx -
+ elDot * iR->src->pData[rSt].rx) / (slDot - elDot);
+ }
+ else
+ {
+ atx =
+ (srDot * iL->src->pData[lEn].rx -
+ erDot * iL->src->pData[lSt].rx) / (srDot - erDot);
+ }
+ atL = srDot / (srDot - erDot);
+ atR = slDot / (slDot - elDot);
+ return true;
+ }
+
+ return true;
+}
+
+int
+Shape::PushIncidence (Shape * a, int cb, int pt, double theta)
+{
+ if (theta < 0 || theta > 1)
+ return -1;
+
+ if (nbInc >= maxInc)
+ {
+ maxInc = 2 * nbInc + 1;
+ iData =
+ (incidenceData *) g_realloc(iData, maxInc * sizeof (incidenceData));
+ }
+ int n = nbInc++;
+ iData[n].nextInc = a->swsData[cb].firstLinkedPoint;
+ iData[n].pt = pt;
+ iData[n].theta = theta;
+ a->swsData[cb].firstLinkedPoint = n;
+ return n;
+}
+
+int
+Shape::CreateIncidence (Shape * a, int no, int nPt)
+{
+ Geom::Point adir, diff;
+ adir = a->eData[no].rdx;
+ diff = getPoint(nPt).x - a->pData[a->getEdge(no).st].rx;
+ double t = dot (diff, adir);
+ t *= a->eData[no].ilength;
+ return PushIncidence (a, no, nPt, t);
+}
+
+int
+Shape::Winding (int nPt) const
+{
+ // the array pData has a variable named askForWindingB
+ // that tells us which edge to go to to find the winding number
+ // of the pint nPt.
+ int askTo = pData[nPt].askForWindingB;
+ if (askTo < 0 || askTo >= numberOfEdges()) // if there is no info there, just return 0
+ return 0;
+ // if the edge is top to bottom, return left winding number otherwise the right winding number
+ // actually, while sweeping, ConvertToShape stores the edge on the immediate left of each point,
+ // hence, we are seeing the winding to the right of this edge and depending on orientation,
+ // right is "left" if edge is top to bottom, right is "right" if edge is bottom to top
+ if (getEdge(askTo).st < getEdge(askTo).en)
+ {
+ return swdData[askTo].leW;
+ }
+ else
+ {
+ return swdData[askTo].riW;
+ }
+ return 0;
+}
+
+int
+Shape::Winding (const Geom::Point px) const
+{
+ int lr = 0, ll = 0, rr = 0;
+
+ // for each edge
+ for (int i = 0; i < numberOfEdges(); i++)
+ {
+ Geom::Point adir, diff, ast, aen;
+ adir = eData[i].rdx;
+
+ ast = pData[getEdge(i).st].rx; // start point of this edge
+ aen = pData[getEdge(i).en].rx; // end point of this edge
+
+ int nWeight = eData[i].weight; // weight of this edge
+
+ // this block checks if the vertical lines crossing the start and end points of the edge
+ // covers the point px or not. See the first figure in the header documentation to see
+ // what I mean. The figure shows two contours one inside another. It shows the point px
+ // and the current edge that the loop is processing is drawn in black color. Two dashed
+ // vertical lines create a region. This block of code checks if the point px lies within
+ // that region or not. Because if it doesn't, we really don't care about this edge at all
+ // then.
+ if (ast[0] < aen[0])
+ {
+ if (ast[0] > px[0])
+ continue;
+ if (aen[0] < px[0])
+ continue;
+ }
+ else
+ {
+ if (ast[0] < px[0])
+ continue;
+ if (aen[0] > px[0])
+ continue;
+ }
+
+ // the situations in these blocks are explained by the second and third figure in the header documentation
+ // the fourth figure along with the documentation there describe what ll and rr really do
+ if (ast[0] == px[0])
+ {
+ if (ast[1] >= px[1])
+ continue;
+ if (aen[0] == px[0])
+ continue;
+ if (aen[0] < px[0])
+ ll += nWeight;
+ else
+ rr -= nWeight;
+ continue;
+ }
+ if (aen[0] == px[0])
+ {
+ if (aen[1] >= px[1])
+ continue;
+ if (ast[0] == px[0])
+ continue;
+ if (ast[0] < px[0])
+ ll -= nWeight;
+ else
+ rr += nWeight;
+ continue;
+ }
+
+ // if the edge is below the point, it doesn't cut the ray at all
+ // so we don't care about it
+ if (ast[1] < aen[1])
+ {
+ if (ast[1] >= px[1])
+ continue;
+ }
+ else
+ {
+ if (aen[1] >= px[1])
+ continue;
+ }
+
+ // a vector from the edge start point to our point px whose winding we wanna calculate
+ diff = px - ast;
+ double cote = cross(adir, diff); // cross from edge vector to diff vector to figure out the orientation
+ if (cote == 0)
+ continue;
+ if (cote < 0)
+ {
+ if (ast[0] > px[0])
+ lr += nWeight;
+ }
+ else
+ {
+ if (ast[0] < px[0])
+ lr -= nWeight;
+ }
+ }
+ return lr + (ll + rr) / 2; // lr comes as it is, ll and rr get divided by two due to the reason I mention in the header file docs
+}
+
+// merging duplicate points and edges
+int
+Shape::AssemblePoints (int st, int en)
+{
+ if (en > st) {
+ for (int i = st; i < en; i++) pData[i].oldInd = i;
+// SortPoints(st,en-1);
+ SortPointsByOldInd (st, en - 1); // SortPointsByOldInd() is required here, because of the edges we have
+ // associated with the point for later computation of winding numbers.
+ // specifically, we need the first point we treated, it's the only one with a valid
+ // associated edge (man, that was a nice bug).
+ for (int i = st; i < en; i++) pData[pData[i].oldInd].newInd = i;
+
+ int lastI = st;
+ for (int i = st; i < en; i++) {
+ pData[i].pending = lastI++;
+ if (i > st && getPoint(i - 1).x[0] == getPoint(i).x[0] && getPoint(i - 1).x[1] == getPoint(i).x[1]) {
+ pData[i].pending = pData[i - 1].pending;
+ if (pData[pData[i].pending].askForWindingS == nullptr) {
+ pData[pData[i].pending].askForWindingS = pData[i].askForWindingS;
+ pData[pData[i].pending].askForWindingB = pData[i].askForWindingB;
+ } else {
+ if (pData[pData[i].pending].askForWindingS == pData[i].askForWindingS
+ && pData[pData[i].pending].askForWindingB == pData[i].askForWindingB) {
+ // meme bord, c bon
+ } else {
+ // meme point, mais pas le meme bord: ouille!
+ // il faut prendre le bord le plus a gauche
+ // en pratique, n'arrive que si 2 maxima sont dans la meme case -> le mauvais choix prend une arete incidente
+ // au bon choix
+// printf("doh");
+ }
+ }
+ lastI--;
+ } else {
+ if (i > pData[i].pending) {
+ _pts[pData[i].pending].x = getPoint(i).x;
+ pData[pData[i].pending].rx = getPoint(i).x;
+ pData[pData[i].pending].askForWindingS = pData[i].askForWindingS;
+ pData[pData[i].pending].askForWindingB = pData[i].askForWindingB;
+ }
+ }
+ }
+ for (int i = st; i < en; i++) pData[i].newInd = pData[pData[i].newInd].pending;
+ return lastI;
+ }
+ return en;
+}
+
+void
+Shape::AssemblePoints (Shape * a)
+{
+ if (hasPoints())
+ {
+ int lastI = AssemblePoints (0, numberOfPoints());
+
+ for (int i = 0; i < a->numberOfEdges(); i++)
+ {
+ a->swsData[i].stPt = pData[a->swsData[i].stPt].newInd;
+ a->swsData[i].enPt = pData[a->swsData[i].enPt].newInd;
+ }
+ for (int i = 0; i < nbInc; i++)
+ iData[i].pt = pData[iData[i].pt].newInd;
+
+ _pts.resize(lastI);
+ }
+}
+void
+Shape::AssembleAretes (FillRule directed)
+{
+ if ( directed == fill_justDont && _has_back_data == false ) {
+ directed=fill_nonZero;
+ }
+
+ // for each point in points
+ for (int i = 0; i < numberOfPoints(); i++) {
+ if (getPoint(i).totalDegree() == 2) { // if simple point with one incoming edge another outgoing edge
+ int cb, cc;
+ cb = getPoint(i).incidentEdge[FIRST]; // the first edge connected to the point
+ cc = getPoint(i).incidentEdge[LAST]; // the last edge connected to the point
+ bool doublon=false;
+ if ((getEdge(cb).st == getEdge(cc).st && getEdge(cb).en == getEdge(cc).en)
+ || (getEdge(cb).st == getEdge(cc).en && getEdge(cb).en == getEdge(cc).en)) doublon=true; // if the start and end edges have same endpoints, it's a doublon edge
+ if ( directed == fill_justDont ) {
+ if ( doublon ) {
+ // depending on pathID, pieceID and tSt reorient cb and cc if needed
+ if ( ebData[cb].pathID > ebData[cc].pathID ) {
+ cc = getPoint(i).incidentEdge[FIRST]; // on swappe pour enlever cc
+ cb = getPoint(i).incidentEdge[LAST];
+ } else if ( ebData[cb].pathID == ebData[cc].pathID ) {
+ if ( ebData[cb].pieceID > ebData[cc].pieceID ) {
+ cc = getPoint(i).incidentEdge[FIRST]; // on swappe pour enlever cc
+ cb = getPoint(i).incidentEdge[LAST];
+ } else if ( ebData[cb].pieceID == ebData[cc].pieceID ) {
+ if ( ebData[cb].tSt > ebData[cc].tSt ) {
+ cc = getPoint(i).incidentEdge[FIRST]; // on swappe pour enlever cc
+ cb = getPoint(i).incidentEdge[LAST];
+ }
+ }
+ }
+ }
+ if ( doublon ) eData[cc].weight = 0; // make cc's weight zero
+ } else {
+ }
+ if ( doublon ) {
+ if (getEdge(cb).st == getEdge(cc).st) { // if both edges share same start point
+ eData[cb].weight += eData[cc].weight; // you get double weight
+ } else {
+ eData[cb].weight -= eData[cc].weight; // if one's start is other's end, you subtract weight
+ }
+ eData[cc].weight = 0; // remove cc (set weight to zero)
+
+ // winding number seed stuff
+ if (swsData[cc].firstLinkedPoint >= 0) {
+ int cp = swsData[cc].firstLinkedPoint;
+ while (cp >= 0) {
+ pData[cp].askForWindingB = cb;
+ cp = pData[cp].nextLinkedPoint;
+ }
+ if (swsData[cb].firstLinkedPoint < 0) {
+ swsData[cb].firstLinkedPoint = swsData[cc].firstLinkedPoint;
+ } else {
+ int ncp = swsData[cb].firstLinkedPoint;
+ while (pData[ncp].nextLinkedPoint >= 0) {
+ ncp = pData[ncp].nextLinkedPoint;
+ }
+ pData[ncp].nextLinkedPoint = swsData[cc].firstLinkedPoint;
+ }
+ }
+
+ // disconnect start and end of cc
+ DisconnectStart (cc);
+ DisconnectEnd (cc);
+
+ if (numberOfEdges() > 1) {
+ int cp = swsData[numberOfEdges() - 1].firstLinkedPoint;
+ while (cp >= 0) {
+ pData[cp].askForWindingB = cc;
+ cp = pData[cp].nextLinkedPoint;
+ }
+ }
+ // swap cc with last edge
+ SwapEdges (cc, numberOfEdges() - 1);
+ if (cb == numberOfEdges() - 1) {
+ cb = cc;
+ }
+ // pop back the last one (to completely remove it from the array)
+ _aretes.pop_back();
+ }
+ } else {
+ int cb;
+ cb = getPoint(i).incidentEdge[FIRST];
+ while (cb >= 0 && cb < numberOfEdges()) {
+ int other = Other (i, cb);
+ int cc;
+ cc = getPoint(i).incidentEdge[FIRST];
+ while (cc >= 0 && cc < numberOfEdges()) {
+ int ncc = NextAt (i, cc);
+ bool doublon=false;
+ if (cc != cb && Other (i, cc) == other ) doublon=true;
+ if ( directed == fill_justDont ) {
+ if ( doublon ) {
+ if ( ebData[cb].pathID > ebData[cc].pathID ) {
+ doublon=false;
+ } else if ( ebData[cb].pathID == ebData[cc].pathID ) {
+ if ( ebData[cb].pieceID > ebData[cc].pieceID ) {
+ doublon=false;
+ } else if ( ebData[cb].pieceID == ebData[cc].pieceID ) {
+ if ( ebData[cb].tSt > ebData[cc].tSt ) {
+ doublon=false;
+ }
+ }
+ }
+ }
+ if ( doublon ) eData[cc].weight = 0;
+ } else {
+ }
+ if ( doublon ) {
+// if (cc != cb && Other (i, cc) == other) {
+ // doublon
+ if (getEdge(cb).st == getEdge(cc).st) {
+ eData[cb].weight += eData[cc].weight;
+ } else {
+ eData[cb].weight -= eData[cc].weight;
+ }
+ eData[cc].weight = 0;
+
+ if (swsData[cc].firstLinkedPoint >= 0) {
+ int cp = swsData[cc].firstLinkedPoint;
+ while (cp >= 0) {
+ pData[cp].askForWindingB = cb;
+ cp = pData[cp].nextLinkedPoint;
+ }
+ if (swsData[cb].firstLinkedPoint < 0) {
+ swsData[cb].firstLinkedPoint = swsData[cc].firstLinkedPoint;
+ } else {
+ int ncp = swsData[cb].firstLinkedPoint;
+ while (pData[ncp].nextLinkedPoint >= 0) {
+ ncp = pData[ncp].nextLinkedPoint;
+ }
+ pData[ncp].nextLinkedPoint = swsData[cc].firstLinkedPoint;
+ }
+ }
+
+ DisconnectStart (cc);
+ DisconnectEnd (cc);
+ if (numberOfEdges() > 1) {
+ int cp = swsData[numberOfEdges() - 1].firstLinkedPoint;
+ while (cp >= 0) {
+ pData[cp].askForWindingB = cc;
+ cp = pData[cp].nextLinkedPoint;
+ }
+ }
+ SwapEdges (cc, numberOfEdges() - 1);
+ if (cb == numberOfEdges() - 1) {
+ cb = cc;
+ }
+ if (ncc == numberOfEdges() - 1) {
+ ncc = cc;
+ }
+ _aretes.pop_back();
+ }
+ cc = ncc;
+ }
+ cb = NextAt (i, cb);
+ }
+ }
+ }
+
+ if ( directed == fill_justDont ) {
+ for (int i = 0; i < numberOfEdges(); i++) {
+ if (eData[i].weight == 0) {
+ // SubEdge(i);
+ // i--;
+ } else {
+ if (eData[i].weight < 0) Inverse (i);
+ }
+ }
+ } else {
+ for (int i = 0; i < numberOfEdges(); i++) {
+ if (eData[i].weight == 0) {
+ // SubEdge(i);
+ // i--;
+ } else {
+ if (eData[i].weight < 0) Inverse (i);
+ }
+ }
+ }
+ }
+void
+Shape::GetWindings (Shape * /*a*/, Shape * /*b*/, BooleanOp /*mod*/, bool brutal)
+{
+ // preparation du parcours
+ for (int i = 0; i < numberOfEdges(); i++)
+ {
+ swdData[i].misc = nullptr;
+ swdData[i].precParc = swdData[i].suivParc = -1;
+ }
+
+ // we make sure that the edges are sorted. What this means is that for each point, all
+ // the edges that are attached to it are arranged in the linked list according to
+ // clockwise order (of their spatial orientation)
+ // chainage
+ SortEdges ();
+
+ int searchInd = 0;
+
+ int lastPtUsed = 0;
+ // okay now let's see what this outer most loop is supposed to do. Look, you can have a directed graph
+ // with multiple paths that don't touch each other. For example a rectangle inside another rectangle.
+ // If you just start at the first point and follow the edges moving around, you'd have explored one
+ // sub-graph but you wouldn't even touch the others. This outer loop ensures that all the points have
+ // been walked over. We start at the first point and start exploring. When we reach the end of that
+ // sub-graph, we update lastPtUsed and this outerloop will check if there are still points remaining
+ // to be explored, if yes, we start with the first point (that we haven't touched yet)
+ do
+ {
+ int startBord = -1;
+ int outsideW = 0; // the winding number outside (to the top left) of the first point in a sub-graph
+ {
+ int fi = 0;
+ // ignore all points that don't have any edges attached
+ for (fi = lastPtUsed; fi < numberOfPoints(); fi++)
+ {
+ if (getPoint(fi).incidentEdge[FIRST] >= 0 && swdData[getPoint(fi).incidentEdge[FIRST]].misc == nullptr)
+ break;
+ }
+ lastPtUsed = fi + 1;
+ if (fi < numberOfPoints())
+ {
+ // get the first edge attached to the first point
+ int bestB = getPoint(fi).incidentEdge[FIRST];
+ if (bestB >= 0)
+ {
+ // let's start with this edge
+ startBord = bestB;
+ // is the first point the first in the array? if yes, this ensure it's at the top most and left most position.
+ // Hence the winding number must be zero (since that region is literally outside everything)
+ if (fi == 0)
+ {
+ outsideW = 0;
+ }
+ else
+ {
+ // you can either compute the winding number by iterating through all the edges
+ // basically that would work by seeing how many edges a ray from (0, +infty) would cross
+ // and in which order
+ if (brutal)
+ {
+ outsideW = Winding (getPoint(fi).x);
+ }
+ // or we can get the winding number for that point computed by the sweepline.. this is pretty
+ // interesting.
+ else
+ {
+ outsideW = Winding (fi);
+ }
+ }
+ // TODO: Look at this piece
+ if ( getPoint(fi).totalDegree() == 1 ) {
+ if ( fi == getEdge(startBord).en ) {
+ if ( eData[startBord].weight == 0 ) {
+ // on se contente d'inverser
+ Inverse(startBord);
+ } else {
+ // on passe le askForWinding (sinon ca va rester startBord)
+ pData[getEdge(startBord).st].askForWindingB=pData[getEdge(startBord).en].askForWindingB;
+ }
+ }
+ }
+ if (getEdge(startBord).en == fi)
+ outsideW += eData[startBord].weight;
+ }
+ }
+ }
+ if (startBord >= 0)
+ {
+ // now start from this edge
+ // parcours en profondeur pour mettre les leF et riF a leurs valeurs
+ swdData[startBord].misc = (void *) 1;
+ // setting the winding numbers for this edge
+ // one question I had was, would these values for the first edge will always be valid?
+ // The answer is yes. Due to the fact that edges are sorted clockwise, and that
+ // we start with the top most (and leftmost if mutliple top most points exist), and that
+ // there is a piece of code above that adds weight to start edge if the edge ends at the current
+ // point, I think these will always be correct values.
+ swdData[startBord].leW = outsideW;
+ swdData[startBord].riW = outsideW - eData[startBord].weight;
+// if ( doDebug ) printf("part de %d\n",startBord);
+ // curBord is the current edge that we are at
+ int curBord = startBord;
+ // curDir is the direction, true means we are going in the direction of the edge vector, false means
+ // we are going in the direction opposite to the edge vector
+ bool curDir = true;
+ swdData[curBord].precParc = -1;
+ swdData[curBord].suivParc = -1;
+ // the depth first search
+ do
+ {
+ int cPt;
+ // if curDir is true, we are going along the edge, so get the end point
+ if (curDir)
+ cPt = getEdge(curBord).en;
+ else // if curDir is false, we are going opposite to the edge, so get the start point
+ cPt = getEdge(curBord).st;
+
+ // start finding the next edge to move to
+ int nb = curBord;
+// if ( doDebug ) printf("de curBord= %d avec leF= %d et riF= %d -> ",curBord,swdData[curBord].leW,swdData[curBord].riW);
+ do
+ {
+ int nnb = -1;
+ // see the diagram attached in the header file documentation of this function to see the
+ // four situations that can come up.
+ // outsideW here does not mean outside winding, in fact it means inside winding.
+ // if we are going along the edge, we save the right winding number for later use
+ if (getEdge(nb).en == cPt)
+ {
+ outsideW = swdData[nb].riW;
+ nnb = CyclePrevAt (cPt, nb); // get the prev edge, since sorting was clockwise, this means get the first counter-clockwise edge
+ }
+ // if we are going against the edge, we save it's left winding number for later use
+ else
+ {
+ outsideW = swdData[nb].leW;
+ nnb = CyclePrevAt (cPt, nb);
+ }
+ if (nnb == nb) // if we didn't get any "new" edge
+ {
+ // cul-de-sac
+ nb = -1;
+ break;
+ }
+ nb = nnb;
+ } // you can break for three reasons from this loop: having no other edge, we got the same one we started on, edge hasn't been visited yet
+ while (nb >= 0 && nb != curBord && swdData[nb].misc != nullptr);
+ // in the beginning, you'd break from the upper loop due to the misc condition and later on, you'll break due to nb != curBord which
+ // means we need to start backtracking
+ if (nb < 0 || nb == curBord) // backtracking block
+ {
+ // retour en arriere
+ // so if we are here, we couldn't get any new edge that we haven't seen yet
+ int oPt;
+ // we wanna find the previous point (since we are going back)
+ // if curDir is True, we were going along the edge, so get the start point (going backwards u see)
+ if (curDir)
+ oPt = getEdge(curBord).st;
+ else // if curDir is false, we were going against edge, so get the end point (going backwards)
+ oPt = getEdge(curBord).en;
+ curBord = swdData[curBord].precParc; // make current edge the previous one in traversal (back tracking)
+// if ( doDebug ) printf("retour vers %d\n",curBord);
+ if (curBord < 0) // if no edge to go back to, break
+ break;
+ if (oPt == getEdge(curBord).en) // if this new "current edge" ends at that point, curDir should be true, since we ideally have to go forward
+ curDir = true;
+ else // otherwise set it to false, so ideal direction would be against edge
+ curDir = false;
+ // I say ideal because this this is how backtracking is, if you have nothing new to go forward to, you go back once and see
+ // if there is another new edge to go forward to, if not you go back again, and you keep doing this until the point comes where
+ // you have nothing to go back to and then you break from the loop
+ }
+ else // okay we have new edge to compute windings
+ {
+ swdData[nb].misc = (void *) 1; // we visited this edge, so mark that
+ swdData[nb].ind = searchInd++; // probably for use later on?
+ if (cPt == getEdge(nb).st) // this outsideW is the winding stored before, see the diagram in header file
+ {
+ swdData[nb].riW = outsideW;
+ swdData[nb].leW = outsideW + eData[nb].weight;
+ }
+ else
+ {
+ swdData[nb].leW = outsideW;
+ swdData[nb].riW = outsideW - eData[nb].weight;
+ }
+ // maintaining the stack of traversal
+ swdData[nb].precParc = curBord;
+ swdData[curBord].suivParc = nb;
+ // this edge becomes current edge now
+ curBord = nb;
+// if ( doDebug ) printf("suite %d\n",curBord);
+ // set direction depending on how this edge is oriented
+ if (cPt == getEdge(nb).en)
+ curDir = false;
+ else
+ curDir = true;
+ }
+ }
+ while (true /*swdData[curBord].precParc >= 0 */ );
+ // fin du cas non-oriente
+ }
+ }
+ while (lastPtUsed < numberOfPoints());
+// fflush(stdout);
+}
+
+bool
+Shape::TesteIntersection (Shape * ils, Shape * irs, int ilb, int irb,
+ Geom::Point &atx, double &atL, double &atR,
+ bool /*onlyDiff*/)
+{
+ int lSt = ils->getEdge(ilb).st, lEn = ils->getEdge(ilb).en;
+ int rSt = irs->getEdge(irb).st, rEn = irs->getEdge(irb).en;
+ if (lSt == rSt || lSt == rEn)
+ {
+ return false;
+ }
+ if (lEn == rSt || lEn == rEn)
+ {
+ return false;
+ }
+
+ Geom::Point ldir, rdir;
+ ldir = ils->eData[ilb].rdx;
+ rdir = irs->eData[irb].rdx;
+
+ double il = ils->pData[lSt].rx[0], it = ils->pData[lSt].rx[1], ir =
+ ils->pData[lEn].rx[0], ib = ils->pData[lEn].rx[1];
+ if (il > ir)
+ {
+ std::swap(il, ir);
+ }
+ if (it > ib)
+ {
+ std::swap(it, ib);
+ }
+ double jl = irs->pData[rSt].rx[0], jt = irs->pData[rSt].rx[1], jr =
+ irs->pData[rEn].rx[0], jb = irs->pData[rEn].rx[1];
+ if (jl > jr)
+ {
+ std::swap(jl, jr);
+ }
+ if (jt > jb)
+ {
+ std::swap(jt, jb);
+ }
+
+ if (il > jr || it > jb || ir < jl || ib < jt)
+ return false;
+
+ // pre-test
+ {
+ Geom::Point sDiff, eDiff;
+ double slDot, elDot;
+ double srDot, erDot;
+ sDiff = ils->pData[lSt].rx - irs->pData[rSt].rx;
+ eDiff = ils->pData[lEn].rx - irs->pData[rSt].rx;
+ srDot = cross(rdir, sDiff);
+ erDot = cross(rdir, eDiff);
+ if ((srDot >= 0 && erDot >= 0) || (srDot <= 0 && erDot <= 0))
+ return false;
+
+ sDiff = irs->pData[rSt].rx - ils->pData[lSt].rx;
+ eDiff = irs->pData[rEn].rx - ils->pData[lSt].rx;
+ slDot = cross(ldir, sDiff);
+ elDot = cross(ldir, eDiff);
+ if ((slDot >= 0 && elDot >= 0) || (slDot <= 0 && elDot <= 0))
+ return false;
+
+ double slb = slDot - elDot, srb = srDot - erDot;
+ if (slb < 0)
+ slb = -slb;
+ if (srb < 0)
+ srb = -srb;
+ if (slb > srb)
+ {
+ atx =
+ (slDot * irs->pData[rEn].rx - elDot * irs->pData[rSt].rx) / (slDot -
+ elDot);
+ }
+ else
+ {
+ atx =
+ (srDot * ils->pData[lEn].rx - erDot * ils->pData[lSt].rx) / (srDot -
+ erDot);
+ }
+ atL = srDot / (srDot - erDot);
+ atR = slDot / (slDot - elDot);
+ return true;
+ }
+
+ // a mettre en double precision pour des resultats exacts
+ Geom::Point usvs;
+ usvs = irs->pData[rSt].rx - ils->pData[lSt].rx;
+
+ // pas sur de l'ordre des coefs de m
+ Geom::Affine m(ldir[0], ldir[1],
+ rdir[0], rdir[1],
+ 0, 0);
+ double det = m.det();
+
+ double tdet = det * ils->eData[ilb].isqlength * irs->eData[irb].isqlength;
+
+ if (tdet > -0.0001 && tdet < 0.0001)
+ { // ces couillons de vecteurs sont colineaires
+ Geom::Point sDiff, eDiff;
+ double sDot, eDot;
+ sDiff = ils->pData[lSt].rx - irs->pData[rSt].rx;
+ eDiff = ils->pData[lEn].rx - irs->pData[rSt].rx;
+ sDot = cross(rdir, sDiff);
+ eDot = cross(rdir, eDiff);
+
+ atx =
+ (sDot * irs->pData[lEn].rx - eDot * irs->pData[lSt].rx) / (sDot -
+ eDot);
+ atL = sDot / (sDot - eDot);
+
+ sDiff = irs->pData[rSt].rx - ils->pData[lSt].rx;
+ eDiff = irs->pData[rEn].rx - ils->pData[lSt].rx;
+ sDot = cross(ldir, sDiff);
+ eDot = cross(ldir, eDiff);
+
+ atR = sDot / (sDot - eDot);
+
+ return true;
+ }
+
+ // plus de colinearite ni d'extremites en commun
+ m[1] = -m[1];
+ m[2] = -m[2];
+ {
+ double swap = m[0];
+ m[0] = m[3];
+ m[3] = swap;
+ }
+
+ atL = (m[0]* usvs[0] + m[1] * usvs[1]) / det;
+ atR = -(m[2] * usvs[0] + m[3] * usvs[1]) / det;
+ atx = ils->pData[lSt].rx + atL * ldir;
+
+
+ return true;
+}
+
+bool
+Shape::TesteAdjacency (Shape * a, int no, const Geom::Point atx, int nPt,
+ bool push)
+{
+ if (nPt == a->swsData[no].stPt || nPt == a->swsData[no].enPt)
+ return false;
+
+ Geom::Point adir, diff, ast, aen, diff1, diff2, diff3, diff4;
+
+ ast = a->pData[a->getEdge(no).st].rx;
+ aen = a->pData[a->getEdge(no).en].rx;
+
+ adir = a->eData[no].rdx;
+
+ double sle = a->eData[no].length;
+ double ile = a->eData[no].ilength;
+
+ diff = atx - ast;
+
+ double e = IHalfRound(cross(adir, diff) * a->eData[no].isqlength);
+ if (-3 < e && e < 3)
+ {
+ double rad = HalfRound (0.501); // when using single precision, 0.505 is better (0.5 would be the correct value,
+ // but it produces lots of bugs)
+ diff1[0] = diff[0] - rad;
+ diff1[1] = diff[1] - rad;
+ diff2[0] = diff[0] + rad;
+ diff2[1] = diff[1] - rad;
+ diff3[0] = diff[0] + rad;
+ diff3[1] = diff[1] + rad;
+ diff4[0] = diff[0] - rad;
+ diff4[1] = diff[1] + rad;
+ double di1, di2;
+ bool adjacent = false;
+ di1 = cross(adir, diff1);
+ di2 = cross(adir, diff3);
+ if ((di1 < 0 && di2 > 0) || (di1 > 0 && di2 < 0))
+ {
+ adjacent = true;
+ }
+ else
+ {
+ di1 = cross(adir, diff2);
+ di2 = cross(adir, diff4);
+ if ((di1 < 0 && di2 > 0) || (di1 > 0 && di2 < 0))
+ {
+ adjacent = true;
+ }
+ }
+ if (adjacent)
+ {
+ double t = dot (diff, adir);
+ if (t > 0 && t < sle)
+ {
+ if (push)
+ {
+ t *= ile;
+ PushIncidence (a, no, nPt, t);
+ }
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+void
+Shape::CheckAdjacencies (int lastPointNo, int lastChgtPt, Shape * /*shapeHead*/,
+ int /*edgeHead*/)
+{
+ // for each event in chgts
+ for (auto & chgt : chgts)
+ {
+ // get the chgt.ptoNo, which is the point at which the event happened
+ int chLeN = chgt.ptNo;
+ int chRiN = chgt.ptNo;
+ if (chgt.src)
+ {
+ Shape *lS = chgt.src;
+ int lB = chgt.bord;
+ // get the leftRnd and rightRnd of this edge
+ int lftN = lS->swsData[lB].leftRnd;
+ int rgtN = lS->swsData[lB].rightRnd;
+ // expand the range chLeN..chRiN
+ if (lftN < chLeN)
+ chLeN = lftN;
+ if (rgtN > chRiN)
+ chRiN = rgtN;
+ // check each point from lastChgtPt to leftN-1 for a possible adjacency with
+ // the edge, if detected mark it by modifying leftRnd
+ // Note we do this in reverse order by starting at the point closer to the
+ // edge first. If an adjacency is not detected, we immediately break since
+ // if a point is not adjacent, another on its left won't be adjacent either
+// for (int n=lftN;n<=rgtN;n++) CreateIncidence(lS,lB,n);
+ for (int n = lftN - 1; n >= lastChgtPt; n--)
+ {
+ if (TesteAdjacency (lS, lB, getPoint(n).x, n, false) ==
+ false)
+ break;
+ lS->swsData[lB].leftRnd = n;
+ }
+ // check each point from rgtN+1 to lastPointNo (not included) for a possible adjacency with
+ // the edge, if detected mark it by modifying rightRnd
+ // If an adjacency is not detected, we immediately break since if a point
+ // is not adjacent, another one to its right won't be either.
+ for (int n = rgtN + 1; n < lastPointNo; n++)
+ {
+ if (TesteAdjacency (lS, lB, getPoint(n).x, n, false) ==
+ false)
+ break;
+ lS->swsData[lB].rightRnd = n;
+ }
+ }
+ // totally identical to the block above
+ if (chgt.osrc)
+ {
+ Shape *rS = chgt.osrc;
+ int rB = chgt.obord;
+ int lftN = rS->swsData[rB].leftRnd;
+ int rgtN = rS->swsData[rB].rightRnd;
+ if (lftN < chLeN)
+ chLeN = lftN;
+ if (rgtN > chRiN)
+ chRiN = rgtN;
+// for (int n=lftN;n<=rgtN;n++) CreateIncidence(rS,rB,n);
+ for (int n = lftN - 1; n >= lastChgtPt; n--)
+ {
+ if (TesteAdjacency (rS, rB, getPoint(n).x, n, false) ==
+ false)
+ break;
+ rS->swsData[rB].leftRnd = n;
+ }
+ for (int n = rgtN + 1; n < lastPointNo; n++)
+ {
+ if (TesteAdjacency (rS, rB, getPoint(n).x, n, false) ==
+ false)
+ break;
+ rS->swsData[rB].rightRnd = n;
+ }
+ }
+ // very interesting part, deals with edges to the left in the sweepline at the time
+ // the event took place
+ if (chgt.lSrc)
+ {
+ // is the left edge's leftRnd smaller than lastChgtPt, basically this is a way to check
+ // if leftRnd got updated in the previous iteration of the main loop of Shape::ConvertToShape
+ // or not.
+ if (chgt.lSrc->swsData[chgt.lBrd].leftRnd < lastChgtPt)
+ {
+ // get the left edge and its shape
+ Shape *nSrc = chgt.lSrc;
+ int nBrd = chgt.lBrd /*,nNo=chgts[cCh].ptNo */ ;
+ bool hit;
+
+ // iterate through the linked list of edges to the left
+ do
+ {
+ hit = false; // adjacency got detected?
+ // check all points from chRiN to chLeN
+ // we go right to left
+ for (int n = chRiN; n >= chLeN; n--)
+ {
+ // test if the point is adjacent to the edge
+ if (TesteAdjacency
+ (nSrc, nBrd, getPoint(n).x, n, false))
+ {
+ // has the leftRnd been updated in previous iteration? if no? set it directly
+ if (nSrc->swsData[nBrd].leftRnd < lastChgtPt)
+ {
+ nSrc->swsData[nBrd].leftRnd = n;
+ nSrc->swsData[nBrd].rightRnd = n;
+ }
+ else // if yes, we do some checking and only update if it expands the span
+ {
+ if (n < nSrc->swsData[nBrd].leftRnd)
+ nSrc->swsData[nBrd].leftRnd = n;
+ if (n > nSrc->swsData[nBrd].rightRnd)
+ nSrc->swsData[nBrd].rightRnd = n;
+ }
+ hit = true;
+ }
+ }
+ // test all points between lastChgtPt and chLeN - 1
+ for (int n = chLeN - 1; n >= lastChgtPt; n--)
+ {
+ if (TesteAdjacency
+ (nSrc, nBrd, getPoint(n).x, n, false) == false)
+ break;
+ if (nSrc->swsData[nBrd].leftRnd < lastChgtPt)
+ {
+ nSrc->swsData[nBrd].leftRnd = n;
+ nSrc->swsData[nBrd].rightRnd = n;
+ }
+ else
+ {
+ if (n < nSrc->swsData[nBrd].leftRnd)
+ nSrc->swsData[nBrd].leftRnd = n;
+ if (n > nSrc->swsData[nBrd].rightRnd)
+ nSrc->swsData[nBrd].rightRnd = n;
+ }
+ hit = true;
+ }
+ // if no adjacency got detected, no point in going further left so break, if yes
+ // we continue and see if we can repeat the process on the edge to the left
+ // and so on (basically going left detecting adjacencies)
+ if (hit)
+ {
+ // get the edge on the left, if non exist, break
+ SweepTree *node =
+ static_cast < SweepTree * >(nSrc->swsData[nBrd].misc);
+ if (node == nullptr)
+ break;
+ node = static_cast < SweepTree * >(node->elem[LEFT]);
+ if (node == nullptr)
+ break;
+ nSrc = node->src;
+ nBrd = node->bord;
+ // the edge on the left, did its leftRnd update in the previous iteration of the main loop if ConvertToShape?
+ if (nSrc->swsData[nBrd].leftRnd >= lastChgtPt)
+ break;
+ }
+ }
+ while (hit);
+
+ }
+ }
+ // same thing but to the right side?
+ if (chgt.rSrc)
+ {
+ if (chgt.rSrc->swsData[chgt.rBrd].leftRnd < lastChgtPt)
+ {
+ Shape *nSrc = chgt.rSrc;
+ int nBrd = chgt.rBrd /*,nNo=chgts[cCh].ptNo */ ;
+ bool hit;
+ do
+ {
+ hit = false;
+ // test points between chLeN and chRiN for adjacency
+ // but go left to right
+ for (int n = chLeN; n <= chRiN; n++)
+ {
+ if (TesteAdjacency
+ (nSrc, nBrd, getPoint(n).x, n, false))
+ {
+ if (nSrc->swsData[nBrd].leftRnd < lastChgtPt)
+ {
+ nSrc->swsData[nBrd].leftRnd = n;
+ nSrc->swsData[nBrd].rightRnd = n;
+ }
+ else
+ {
+ if (n < nSrc->swsData[nBrd].leftRnd)
+ nSrc->swsData[nBrd].leftRnd = n;
+ if (n > nSrc->swsData[nBrd].rightRnd)
+ nSrc->swsData[nBrd].rightRnd = n;
+ }
+ hit = true;
+ }
+ }
+ // testing points between chRiN and lastPointNo for adjacency
+ for (int n = chRiN + 1; n < lastPointNo; n++)
+ {
+ if (TesteAdjacency
+ (nSrc, nBrd, getPoint(n).x, n, false) == false)
+ break;
+ if (nSrc->swsData[nBrd].leftRnd < lastChgtPt)
+ {
+ nSrc->swsData[nBrd].leftRnd = n;
+ nSrc->swsData[nBrd].rightRnd = n;
+ }
+ else
+ {
+ if (n < nSrc->swsData[nBrd].leftRnd)
+ nSrc->swsData[nBrd].leftRnd = n;
+ if (n > nSrc->swsData[nBrd].rightRnd)
+ nSrc->swsData[nBrd].rightRnd = n;
+ }
+ hit = true;
+ }
+ if (hit)
+ {
+ SweepTree *node =
+ static_cast < SweepTree * >(nSrc->swsData[nBrd].misc);
+ if (node == nullptr)
+ break;
+ node = static_cast < SweepTree * >(node->elem[RIGHT]);
+ if (node == nullptr)
+ break;
+ nSrc = node->src;
+ nBrd = node->bord;
+ if (nSrc->swsData[nBrd].leftRnd >= lastChgtPt)
+ break;
+ }
+ }
+ while (hit);
+ }
+ }
+ }
+}
+
+
+void Shape::AddChgt(int lastPointNo, int lastChgtPt, Shape * &shapeHead,
+ int &edgeHead, sTreeChangeType type, Shape * lS, int lB, Shape * rS,
+ int rB)
+{
+ // fill in the event details and push it
+ sTreeChange c;
+ c.ptNo = lastPointNo;
+ c.type = type;
+ c.src = lS;
+ c.bord = lB;
+ c.osrc = rS;
+ c.obord = rB;
+ chgts.push_back(c);
+ // index of the newly added event
+ const int nCh = chgts.size() - 1;
+
+ /* FIXME: this looks like a cut and paste job */
+
+ if (lS) {
+ // if there is an edge to the left, mark it in lSrc
+ SweepTree *lE = static_cast < SweepTree * >(lS->swsData[lB].misc);
+ if (lE && lE->elem[LEFT]) {
+ SweepTree *llE = static_cast < SweepTree * >(lE->elem[LEFT]);
+ chgts[nCh].lSrc = llE->src;
+ chgts[nCh].lBrd = llE->bord;
+ } else {
+ chgts[nCh].lSrc = nullptr;
+ chgts[nCh].lBrd = -1;
+ }
+
+ // lastChgtPt is same as lastPointNo if lastPointNo is the leftmost point in that y level and it's the
+ // left most point on the y level of lastPointNo otherwise
+ // leftRnd will be smaller than lastChgtPt if the last time the edge participated in any event (edge addition/intersection)
+ // was before lastChgtPt.
+ if (lS->swsData[lB].leftRnd < lastChgtPt) {
+ lS->swsData[lB].leftRnd = lastPointNo; // set leftRnd to the current point
+ lS->swsData[lB].nextSh = shapeHead; // add this edge in the linked list
+ lS->swsData[lB].nextBo = edgeHead;
+ edgeHead = lB;
+ shapeHead = lS;
+ } else { // If an event occured to the edge after lastChgtPt (which is possible, for example, a horizontal edge
+ // that intersects with other edges.
+ // get the leftRnd already
+ int old = lS->swsData[lB].leftRnd;
+ // This seems really weird. I suspect this if statement will never be true in a top to bottom sweepline
+ // see if we reached this point, it means old >= lastChgtPt
+ // and lastChgtPt is the leftmost point at the current y level of lastPointNo
+ // so how can old have an x greater than lastPoint? The sweepline hasn't reached that position yet!
+ if (getPoint(old).x[0] > getPoint(lastPointNo).x[0]) {
+ lS->swsData[lB].leftRnd = lastPointNo;
+ }
+ }
+ // same logic as in the upper block
+ if (lS->swsData[lB].rightRnd < lastChgtPt) {
+ lS->swsData[lB].rightRnd = lastPointNo;
+ } else {
+ int old = lS->swsData[lB].rightRnd;
+ if (getPoint(old).x[0] < getPoint(lastPointNo).x[0])
+ lS->swsData[lB].rightRnd = lastPointNo;
+ }
+ }
+
+ // it's an intersection event and rS is the right edge's shape and rB is the
+ // right edge
+ if (rS) {
+ // get the edge on the right and set it in rBrd and rSrc
+ SweepTree *rE = static_cast < SweepTree * >(rS->swsData[rB].misc);
+ if (rE->elem[RIGHT]) {
+ SweepTree *rrE = static_cast < SweepTree * >(rE->elem[RIGHT]);
+ chgts[nCh].rSrc = rrE->src;
+ chgts[nCh].rBrd = rrE->bord;
+ } else {
+ chgts[nCh].rSrc = nullptr;
+ chgts[nCh].rBrd = -1;
+ }
+
+ // same code as above, except that it's on rS
+ if (rS->swsData[rB].leftRnd < lastChgtPt) {
+ rS->swsData[rB].leftRnd = lastPointNo;
+ rS->swsData[rB].nextSh = shapeHead;
+ rS->swsData[rB].nextBo = edgeHead;
+ edgeHead = rB;
+ shapeHead = rS;
+ } else {
+ int old = rS->swsData[rB].leftRnd;
+ if (getPoint(old).x[0] > getPoint(lastPointNo).x[0]) {
+ rS->swsData[rB].leftRnd = lastPointNo;
+ }
+ }
+ if (rS->swsData[rB].rightRnd < lastChgtPt) {
+ rS->swsData[rB].rightRnd = lastPointNo;
+ } else {
+ int old = rS->swsData[rB].rightRnd;
+ if (getPoint(old).x[0] < getPoint(lastPointNo).x[0])
+ rS->swsData[rB].rightRnd = lastPointNo;
+ }
+ } else { // if rS wasn't set, this is not an intersection event, so
+ // check if there is an edge to the right and set rBrd
+ SweepTree *lE = static_cast < SweepTree * >(lS->swsData[lB].misc);
+ if (lE && lE->elem[RIGHT]) {
+ SweepTree *rlE = static_cast < SweepTree * >(lE->elem[RIGHT]);
+ chgts[nCh].rSrc = rlE->src;
+ chgts[nCh].rBrd = rlE->bord;
+ } else {
+ chgts[nCh].rSrc = nullptr;
+ chgts[nCh].rBrd = -1;
+ }
+ }
+}
+
+// is this a debug function? It's calling localized "printf" ...
+void
+Shape::Validate ()
+{
+ for (int i = 0; i < numberOfPoints(); i++)
+ {
+ pData[i].rx = getPoint(i).x;
+ }
+ for (int i = 0; i < numberOfEdges(); i++)
+ {
+ eData[i].rdx = getEdge(i).dx;
+ }
+ for (int i = 0; i < numberOfEdges(); i++)
+ {
+ for (int j = i + 1; j < numberOfEdges(); j++)
+ {
+ Geom::Point atx;
+ double atL, atR;
+ if (TesteIntersection (this, this, i, j, atx, atL, atR, false))
+ {
+ printf ("%i %i %f %f di=%f %f dj=%f %f\n", i, j, atx[0],atx[1],getEdge(i).dx[0],getEdge(i).dx[1],getEdge(j).dx[0],getEdge(j).dx[1]);
+ }
+ }
+ }
+ fflush (stdout);
+}
+
+void
+Shape::CheckEdges (int lastPointNo, int lastChgtPt, Shape * a, Shape * b,
+ BooleanOp mod)
+{
+
+ for (auto & chgt : chgts)
+ {
+ // if an edge addition event, we want to set curPoint to the point at which
+ // the edge was added. chgt.ptNo is automatically updated after any possible sorting and merging
+ // so thats good too :-)
+ if (chgt.type == 0)
+ {
+ Shape *lS = chgt.src;
+ int lB = chgt.bord;
+ lS->swsData[lB].curPoint = chgt.ptNo;
+ }
+ }
+ // for each event in events
+ for (auto & chgt : chgts)
+ {
+// int chLeN=chgts[cCh].ptNo;
+// int chRiN=chgts[cCh].ptNo;
+ // do the main edge (by do I mean process it, see if anything needs to be drawn, if yes, draw it)
+ if (chgt.src)
+ {
+ Shape *lS = chgt.src;
+ int lB = chgt.bord;
+ Avance (lastPointNo, lastChgtPt, lS, lB, a, b, mod);
+ }
+ // do the other edge (the right in intersection, wont exist if chgt wasn't an intersection event)
+ if (chgt.osrc)
+ {
+ Shape *rS = chgt.osrc;
+ int rB = chgt.obord;
+ Avance (lastPointNo, lastChgtPt, rS, rB, a, b, mod);
+ }
+
+ // See there are few cases due to which an edge will have a leftRnd >= lastChgtPt. Either the
+ // edge had some event associated with it (addition/removal/intersection) at that y level. Or
+ // there was some point in the previous y level that was on top of the edge, and thus an
+ // adjacency was detected and the leftRnd/rightRnd were set accordingly. If you have neither of
+ // these, leftRnd/rightRnd won't be set at all. If the case is former, that the edge had an
+ // event at the previous y level, the blocks above will automatically call Avance on the edge.
+ // However, for the latter, we have no chgt event associated with the edge. Thus, the blocks
+ // below calls Shape::Avance on edges to the left and to the right of the unique (or the left)
+ // edge. However, it's called only if leftRnd >= lastChgtPt.
+ if (chgt.lSrc)
+ {
+ Shape *nSrc = chgt.lSrc;
+ int nBrd = chgt.lBrd;
+ while (nSrc->swsData[nBrd].leftRnd >= // <-- if yes, means some event occured to this event after lastChgtPt or an adjacency was detected
+ lastChgtPt /*&& nSrc->swsData[nBrd].doneTo < lastChgtPt */ )
+ {
+ Avance (lastPointNo, lastChgtPt, nSrc, nBrd, a, b, mod);
+
+ SweepTree *node =
+ static_cast < SweepTree * >(nSrc->swsData[nBrd].misc);
+ if (node == nullptr)
+ break;
+ node = static_cast < SweepTree * >(node->elem[LEFT]);
+ if (node == nullptr)
+ break;
+ nSrc = node->src;
+ nBrd = node->bord;
+ }
+ }
+ if (chgt.rSrc)
+ {
+ Shape *nSrc = chgt.rSrc;
+ int nBrd = chgt.rBrd;
+ while (nSrc->swsData[nBrd].rightRnd >=
+ lastChgtPt /*&& nSrc->swsData[nBrd].doneTo < lastChgtPt */ )
+ {
+ Avance (lastPointNo, lastChgtPt, nSrc, nBrd, a, b, mod);
+
+ SweepTree *node =
+ static_cast < SweepTree * >(nSrc->swsData[nBrd].misc);
+ if (node == nullptr)
+ break;
+ node = static_cast < SweepTree * >(node->elem[RIGHT]);
+ if (node == nullptr)
+ break;
+ nSrc = node->src;
+ nBrd = node->bord;
+ }
+ }
+ }
+}
+
+void
+Shape::Avance (int lastPointNo, int lastChgtPt, Shape * lS, int lB, Shape * /*a*/,
+ Shape * b, BooleanOp mod)
+{
+ double dd = HalfRound (1); // get the smallest step you can take in the rounding grid.
+ bool avoidDiag = false; // triggers some special diagonal avoiding code below
+// if ( lastChgtPt > 0 && pts[lastChgtPt-1].y+dd == pts[lastChgtPt].y ) avoidDiag=true;
+
+ // my best guess is that direct acts kinda as an inverter. If set to true, edges are drawn
+ // in the direction they should be drawn in, if set to false, they are inverted. This is needed
+ // if the edge is coming from shape b and the mod is bool_op_diff or bool_op_symdiff. This is how
+ // Livarot does these boolean operations.
+ bool direct = true;
+ if (lS == b && (mod == bool_op_diff || mod == bool_op_symdiff))
+ direct = false;
+ // get the leftRnd and rightRnd points of the edge lB. For most edges, leftRnd and rightRnd are identical
+ // however when you have a horizontal edge, they can be different.
+ int lftN = lS->swsData[lB].leftRnd;
+ int rgtN = lS->swsData[lB].rightRnd;
+ // doneTo acts as a marker. Once Avance processes an edge at a certain y level, it sets doneTo to the
+ // right most point at that y level. See the end of this function to see how it does this.
+ if (lS->swsData[lB].doneTo < lastChgtPt)
+ {
+ // the last point till which this edge has been drawn
+ int lp = lS->swsData[lB].curPoint;
+ // if the last point exists and lastChgtPt.y is just dd (1 rounded step) bigger than lastpoint.y
+ // in simpler words, the last point drawn is just 1 rounded step above lastChgtPt
+ // Look, if there is a potential edge to draw, that edge is going to have its upper endpoint at lp
+ // and could have the lower endpoint lftN...rgtN whatever, but we are sure that it can't go any lower (downwards)
+ // than lastChgtPt. If this "if" block evalues to true, there is a possibility we might an edge that would
+ // be diagonal for example 1 rounded unit dd down and to the right. We would like to avoid this if there is
+ // a point right below, so we can draw two edges, first down and then right. See the figure in the header
+ // docs to see what I mean
+ if (lp >= 0 && getPoint(lp).x[1] + dd == getPoint(lastChgtPt).x[1])
+ avoidDiag = true;
+ // if the edge is horizontal
+ if (lS->eData[lB].rdx[1] == 0)
+ {
+ // tjs de gauche a droite et pas de diagonale -- > left to right horizonal edge won't be diagonal (since it's horizontal :P)
+ if (lS->eData[lB].rdx[0] >= 0) // edge is left to right
+ {
+ for (int p = lftN; p <= rgtN; p++)
+ {
+ DoEdgeTo (lS, lB, p, direct, true);
+ lp = p;
+ }
+ }
+ else // edge is right to left
+ {
+ for (int p = lftN; p <= rgtN; p++)
+ {
+ DoEdgeTo (lS, lB, p, direct, false);
+ lp = p;
+ }
+ }
+ }
+ else if (lS->eData[lB].rdx[1] > 0) // the edge is top to bottom
+ {
+ if (lS->eData[lB].rdx[0] >= 0) // edge is top to bottom and left to right
+ {
+
+ for (int p = lftN; p <= rgtN; p++) // for the range lftN..rgtN
+ {
+ // if avoidDiag is true, point p is lftN and the point the edge is to be drawn to (lftN) has an x that's
+ // 1 rounded unit (dd) greater than the last point drawn. So basically lftN is one unit down and one unit
+ // to the right of lp
+ if (avoidDiag && p == lftN && getPoint(lftN).x[0] == getPoint(lp).x[0] + dd)
+ {
+ // we would want to avoid the diagonal but only if there is a point (in our directed graph)
+ // just to the left of the original lower endpoint and instead of doing a diagonal, we can create an edge to that point
+ // and then from that point to the original endpoint. see the figure in the header docs to see what I mean
+ if (lftN > 0 && lftN - 1 >= lastChgtPt // if there is a point in the figure just below lp and to the left of lftN
+ && getPoint(lftN - 1).x[0] == getPoint(lp).x[0]) // that point has x equal to that of lp (right below it)
+ {
+ DoEdgeTo (lS, lB, lftN - 1, direct, true); // draw an edge to lftn - 1
+ DoEdgeTo (lS, lB, lftN, direct, true); // then draw an edge to lftN
+ }
+ else
+ {
+ DoEdgeTo (lS, lB, lftN, direct, true);
+ }
+ }
+ else
+ {
+ DoEdgeTo (lS, lB, p, direct, true);
+ }
+ lp = p;
+ }
+ }
+ else
+ {
+
+ for (int p = rgtN; p >= lftN; p--) // top to bottom and right to left
+ {
+ // exactly identical to the code above except that it's a diagonal down and to the left
+ if (avoidDiag && p == rgtN && getPoint(rgtN).x[0] == getPoint(lp).x[0] - dd)
+ {
+ if (rgtN < numberOfPoints() && rgtN + 1 < lastPointNo // refer to first diagram in header docs to see why this condition
+ && getPoint(rgtN + 1).x[0] == getPoint(lp).x[0])
+ {
+ DoEdgeTo (lS, lB, rgtN + 1, direct, true);
+ DoEdgeTo (lS, lB, rgtN, direct, true);
+ }
+ else
+ {
+ DoEdgeTo (lS, lB, rgtN, direct, true);
+ }
+ }
+ else
+ {
+ DoEdgeTo (lS, lB, p, direct, true);
+ }
+ lp = p;
+ }
+ }
+ }
+ else // edge is bottom to top
+ {
+ if (lS->eData[lB].rdx[0] >= 0) // edge is bottom to top and left to right
+ {
+
+ for (int p = rgtN; p >= lftN; p--)
+ {
+ if (avoidDiag && p == rgtN && getPoint(rgtN).x[0] == getPoint(lp).x[0] - dd)
+ {
+ if (rgtN < numberOfPoints() && rgtN + 1 < lastPointNo
+ && getPoint(rgtN + 1).x[0] == getPoint(lp).x[0])
+ {
+ DoEdgeTo (lS, lB, rgtN + 1, direct, false); // draw an edge that goes down
+ DoEdgeTo (lS, lB, rgtN, direct, false); // draw an edge that goes left
+ }
+ else
+ {
+ DoEdgeTo (lS, lB, rgtN, direct, false);
+ }
+ }
+ else
+ {
+ DoEdgeTo (lS, lB, p, direct, false);
+ }
+ lp = p;
+ }
+ }
+ else
+ {
+ // bottom to top and right to left edge
+ for (int p = lftN; p <= rgtN; p++)
+ {
+ // totally identical as the first block that I explain with a figure in the header docs
+ if (avoidDiag && p == lftN && getPoint(lftN).x[0] == getPoint(lp).x[0] + dd)
+ {
+ if (lftN > 0 && lftN - 1 >= lastChgtPt
+ && getPoint(lftN - 1).x[0] == getPoint(lp).x[0])
+ {
+ DoEdgeTo (lS, lB, lftN - 1, direct, false);
+ DoEdgeTo (lS, lB, lftN, direct, false);
+ }
+ else
+ {
+ DoEdgeTo (lS, lB, lftN, direct, false);
+ }
+ }
+ else
+ {
+ DoEdgeTo (lS, lB, p, direct, false);
+ }
+ lp = p;
+ }
+ }
+ }
+ lS->swsData[lB].curPoint = lp;
+ }
+ // see how doneTo is being set to lastPointNo - 1? See the figure in the header docs of this function and you'll see
+ // what lastPointNo is, subtract one and you get the right most point that's just above lastPointNo. This marks that
+ // this edge has been processed til that y level.
+ lS->swsData[lB].doneTo = lastPointNo - 1;
+}
+
+void
+Shape::DoEdgeTo (Shape * iS, int iB, int iTo, bool direct, bool sens)
+{
+ int lp = iS->swsData[iB].curPoint;
+ int ne = -1;
+ if (sens)
+ {
+ if (direct)
+ ne = AddEdge (lp, iTo);
+ else
+ ne = AddEdge (iTo, lp);
+ }
+ else
+ {
+ if (direct)
+ ne = AddEdge (iTo, lp);
+ else
+ ne = AddEdge (lp, iTo);
+ }
+ if (ne >= 0 && _has_back_data)
+ {
+ ebData[ne].pathID = iS->ebData[iB].pathID;
+ ebData[ne].pieceID = iS->ebData[iB].pieceID;
+ if (iS->eData[iB].length < 0.00001)
+ {
+ ebData[ne].tSt = ebData[ne].tEn = iS->ebData[iB].tSt;
+ }
+ else
+ {
+ double bdl = iS->eData[iB].ilength;
+ Geom::Point bpx = iS->pData[iS->getEdge(iB).st].rx;
+ Geom::Point bdx = iS->eData[iB].rdx;
+ Geom::Point psx = getPoint(getEdge(ne).st).x;
+ Geom::Point pex = getPoint(getEdge(ne).en).x;
+ Geom::Point psbx=psx-bpx;
+ Geom::Point pebx=pex-bpx;
+ double pst = dot(psbx,bdx) * bdl;
+ double pet = dot(pebx,bdx) * bdl;
+ pst = iS->ebData[iB].tSt * (1 - pst) + iS->ebData[iB].tEn * pst;
+ pet = iS->ebData[iB].tSt * (1 - pet) + iS->ebData[iB].tEn * pet;
+ ebData[ne].tEn = pet;
+ ebData[ne].tSt = pst;
+ }
+ }
+ iS->swsData[iB].curPoint = iTo;
+ if (ne >= 0)
+ {
+ int cp = iS->swsData[iB].firstLinkedPoint;
+ swsData[ne].firstLinkedPoint = iS->swsData[iB].firstLinkedPoint;
+ while (cp >= 0)
+ {
+ pData[cp].askForWindingB = ne;
+ cp = pData[cp].nextLinkedPoint;
+ }
+ iS->swsData[iB].firstLinkedPoint = -1;
+ }
+}
diff --git a/src/livarot/float-line.cpp b/src/livarot/float-line.cpp
new file mode 100644
index 0000000..9a19729
--- /dev/null
+++ b/src/livarot/float-line.cpp
@@ -0,0 +1,916 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * Implementation of coverage with floating-point values.
+ *//*
+ * Authors:
+ * see git history
+ * Fred
+ *
+ * Copyright (C) 2018 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifdef faster_flatten
+# include <cmath> // std::abs(float)
+#endif
+#include <cstdio>
+#include "livarot/float-line.h"
+#include "livarot/int-line.h"
+#include <cstdio>
+
+FloatLigne::FloatLigne()
+{
+ s_first = s_last = -1;
+}
+
+
+FloatLigne::~FloatLigne()
+= default;
+
+/// Reset the line to empty (boundaries and runs).
+void FloatLigne::Reset()
+{
+ bords.clear();
+ runs.clear();
+ s_first = s_last = -1;
+}
+
+/**
+ * Add a coverage portion.
+ *
+ * \param guess Position from where we should try to insert the first
+ * boundary, or -1 if we don't have a clue.
+ */
+int FloatLigne::AddBord(float spos, float sval, float epos, float eval, int guess)
+{
+// if ( showCopy ) printf("b= %f %f -> %f %f \n",spos,sval,epos,eval);
+ if ( spos >= epos ) {
+ return -1;
+ }
+
+ float pente = (eval - sval) / (epos - spos);
+
+#ifdef faster_flatten
+ if ( std::abs(epos - spos) < 0.001 || std::abs(pente) > 1000 ) {
+ return -1;
+ epos = spos;
+ pente = 0;
+ }
+#endif
+
+ if ( guess >= int(bords.size()) ) {
+ guess = -1;
+ }
+
+ // add the left boundary
+ float_ligne_bord b;
+ int n = bords.size();
+ b.pos = spos;
+ b.val = sval;
+ b.start = true;
+ b.other = n + 1;
+ b.pente = pente;
+ b.s_prev = b.s_next = -1;
+ bords.push_back(b);
+
+ // insert it in the doubly-linked list
+ InsertBord(n, spos, guess);
+
+ // add the right boundary
+ n = bords.size();
+ b.pos = epos;
+ b.val = eval;
+ b.start = false;
+ b.other = n-1;
+ b.pente = pente;
+ b.s_prev = b.s_next = -1;
+ bords.push_back(b);
+
+ // insert it in the doubly-linked list, knowing that boundary at index n-1 is not too far before me
+ InsertBord(n, epos, n - 1);
+
+ return n;
+}
+
+/**
+ * Add a coverage portion.
+ *
+ * \param guess Position from where we should try to insert the first
+ * boundary, or -1 if we don't have a clue.
+ */
+int FloatLigne::AddBord(float spos, float sval, float epos, float eval, float pente, int guess)
+{
+// if ( showCopy ) printf("b= %f %f -> %f %f \n",spos,sval,epos,eval);
+ if ( spos >= epos ) {
+ return -1;
+ }
+
+#ifdef faster_flatten
+ if ( std::abs(epos - spos) < 0.001 || std::abs(pente) > 1000 ) {
+ return -1;
+ epos = spos;
+ pente = 0;
+ }
+#endif
+
+ if ( guess >= int(bords.size()) ) {
+ guess=-1;
+ }
+
+ float_ligne_bord b;
+ int n = bords.size();
+ b.pos = spos;
+ b.val = sval;
+ b.start = true;
+ b.other = n + 1;
+ b.pente = pente;
+ b.s_prev = b.s_next = -1;
+ bords.push_back(b);
+
+ n = bords.size();
+ b.pos = epos;
+ b.val = eval;
+ b.start = false;
+ b.other = n - 1;
+ b.pente = pente;
+ b.s_prev = b.s_next = -1;
+ bords.push_back(b);
+
+ InsertBord(n - 1, spos, guess);
+ InsertBord(n, epos, n - 1);
+/* if ( bords[n-1].s_next < 0 ) {
+ bords[n].s_next=-1;
+ s_last=n;
+
+ bords[n].s_prev=n-1;
+ bords[n-1].s_next=n;
+ } else if ( bords[bords[n-1].s_next].pos >= epos ) {
+ bords[n].s_next=bords[n-1].s_next;
+ bords[bords[n].s_next].s_prev=n;
+
+ bords[n].s_prev=n-1;
+ bords[n-1].s_next=n;
+ } else {
+ int c=bords[bords[n-1].s_next].s_next;
+ while ( c >= 0 && bords[c].pos < epos ) c=bords[c].s_next;
+ if ( c < 0 ) {
+ bords[n].s_prev=s_last;
+ bords[s_last].s_next=n;
+ s_last=n;
+ } else {
+ bords[n].s_prev=bords[c].s_prev;
+ bords[bords[n].s_prev].s_next=n;
+
+ bords[n].s_next=c;
+ bords[c].s_prev=n;
+ }
+
+ }*/
+ return n;
+}
+
+/**
+ * Add a coverage portion.
+ *
+ * \param guess Position from where we should try to insert the last
+ * boundary, or -1 if we don't have a clue.
+ */
+int FloatLigne::AddBordR(float spos, float sval, float epos, float eval, float pente, int guess)
+{
+// if ( showCopy ) printf("br= %f %f -> %f %f \n",spos,sval,epos,eval);
+// return AddBord(spos,sval,epos,eval,pente,guess);
+ if ( spos >= epos ){
+ return -1;
+ }
+
+#ifdef faster_flatten
+ if ( std::abs(epos - spos) < 0.001 || std::abs(pente) > 1000 ) {
+ return -1;
+ epos = spos;
+ pente = 0;
+ }
+#endif
+
+ if ( guess >= int(bords.size()) ) {
+ guess=-1;
+ }
+
+ float_ligne_bord b;
+ int n = bords.size();
+ b.pos = spos;
+ b.val = sval;
+ b.start = true;
+ b.other = n + 1;
+ b.pente = pente;
+ b.s_prev = b.s_next = -1;
+ bords.push_back(b);
+
+ n = bords.size();
+ b.pos = epos;
+ b.val = eval;
+ b.start = false;
+ b.other = n - 1;
+ b.pente = pente;
+ b.s_prev = b.s_next = -1;
+ bords.push_back(b);
+
+ InsertBord(n, epos, guess);
+ InsertBord(n - 1, spos, n);
+
+/* if ( bords[n].s_prev < 0 ) {
+ bords[n-1].s_prev=-1;
+ s_first=n-1;
+
+ bords[n-1].s_next=n;
+ bords[n].s_prev=n-1;
+ } else if ( bords[bords[n].s_prev].pos <= spos ) {
+ bords[n-1].s_prev=bords[n].s_prev;
+ bords[bords[n-1].s_prev].s_next=n-1;
+
+ bords[n-1].s_next=n;
+ bords[n].s_prev=n-1;
+ } else {
+ int c=bords[bords[n].s_prev].s_prev;
+ while ( c >= 0 && bords[c].pos > spos ) c=bords[c].s_prev;
+ if ( c < 0 ) {
+ bords[n-1].s_next=s_first;
+ bords[s_first].s_prev=n-1;
+ s_first=n-1;
+ } else {
+ bords[n-1].s_next=bords[c].s_next;
+ bords[bords[n-1].s_next].s_prev=n-1;
+
+ bords[n-1].s_prev=c;
+ bords[c].s_next=n-1;
+ }
+
+ }*/
+ return n - 1;
+}
+
+/**
+ * Add a coverage portion by appending boundaries at the end of the list.
+ *
+ * This works because we know they are on the right.
+ */
+int FloatLigne::AppendBord(float spos, float sval, float epos, float eval, float pente)
+{
+// if ( showCopy ) printf("b= %f %f -> %f %f \n",spos,sval,epos,eval);
+// return AddBord(spos,sval,epos,eval,pente,s_last);
+ if ( spos >= epos ) {
+ return -1;
+ }
+
+#ifdef faster_flatten
+ if ( std::abs(epos - spos) < 0.001 || std::abs(pente) > 1000 ) {
+ return -1;
+ epos = spos;
+ pente = 0;
+ }
+#endif
+
+ int n = bords.size();
+ float_ligne_bord b;
+ b.pos = spos;
+ b.val = sval;
+ b.start = true;
+ b.other = n + 1;
+ b.pente = pente;
+ b.s_prev = s_last;
+ b.s_next = n + 1;
+ bords.push_back(b);
+
+ if ( s_last >= 0 ) {
+ bords[s_last].s_next = n;
+ }
+
+ if ( s_first < 0 ) {
+ s_first = n;
+ }
+
+ n = bords.size();
+ b.pos = epos;
+ b.val = eval;
+ b.start = false;
+ b.other = n - 1;
+ b.pente = pente;
+ b.s_prev = n - 1;
+ b.s_next = -1;
+ bords.push_back(b);
+
+ s_last = n;
+
+ return n;
+}
+
+
+
+// insertion in a boubly-linked list. nothing interesting here
+void FloatLigne::InsertBord(int no, float /*p*/, int guess)
+{
+// TODO check if ignoring p is bad
+ if ( no < 0 || no >= int(bords.size()) ) {
+ return;
+ }
+
+ if ( s_first < 0 ) {
+ s_first = s_last = no;
+ bords[no].s_prev = -1;
+ bords[no].s_next = -1;
+ return;
+ }
+
+ if ( guess < 0 || guess >= int(bords.size()) ) {
+ int c = s_first;
+ while ( c >= 0 && c < int(bords.size()) && CmpBord(bords[c], bords[no]) < 0 ) {
+ c = bords[c].s_next;
+ }
+
+ if ( c < 0 || c >= int(bords.size()) ) {
+ bords[no].s_prev = s_last;
+ bords[s_last].s_next = no;
+ s_last = no;
+ } else {
+ bords[no].s_prev = bords[c].s_prev;
+ if ( bords[no].s_prev >= 0 ) {
+ bords[bords[no].s_prev].s_next = no;
+ } else {
+ s_first = no;
+ }
+ bords[no].s_next = c;
+ bords[c].s_prev = no;
+ }
+ } else {
+ int c = guess;
+ int stTst = CmpBord(bords[c], bords[no]);
+
+ if ( stTst == 0 ) {
+
+ bords[no].s_prev = bords[c].s_prev;
+ if ( bords[no].s_prev >= 0 ) {
+ bords[bords[no].s_prev].s_next = no;
+ } else {
+ s_first = no;
+ }
+ bords[no].s_next = c;
+ bords[c].s_prev = no;
+
+ } else if ( stTst > 0 ) {
+
+ while ( c >= 0 && c < int(bords.size()) && CmpBord(bords[c], bords[no]) > 0 ) {
+ c = bords[c].s_prev;
+ }
+
+ if ( c < 0 || c >= int(bords.size()) ) {
+ bords[no].s_next = s_first;
+ bords[s_first].s_prev =no; // s_first != -1
+ s_first = no;
+ } else {
+ bords[no].s_next = bords[c].s_next;
+ if ( bords[no].s_next >= 0 ) {
+ bords[bords[no].s_next].s_prev = no;
+ } else {
+ s_last = no;
+ }
+ bords[no].s_prev = c;
+ bords[c].s_next = no;
+ }
+
+ } else {
+
+ while ( c >= 0 && c < int(bords.size()) && CmpBord(bords[c],bords[no]) < 0 ) {
+ c = bords[c].s_next;
+ }
+
+ if ( c < 0 || c >= int(bords.size()) ) {
+ bords[no].s_prev = s_last;
+ bords[s_last].s_next = no;
+ s_last = no;
+ } else {
+ bords[no].s_prev = bords[c].s_prev;
+ if ( bords[no].s_prev >= 0 ) {
+ bords[bords[no].s_prev].s_next = no;
+ } else {
+ s_first = no;
+ }
+ bords[no].s_next = c;
+ bords[c].s_prev = no;
+ }
+ }
+ }
+}
+
+/**
+ * Computes the sum of the coverages of the runs currently being scanned,
+ * of which there are "pending".
+ */
+float FloatLigne::RemainingValAt(float at, int pending)
+{
+ float sum = 0;
+/* int no=firstAc;
+ while ( no >= 0 && no < bords.size() ) {
+ int nn=bords[no].other;
+ sum+=bords[nn].val+(at-bords[nn].pos)*bords[nn].pente;
+// sum+=((at-bords[nn].pos)*bords[no].val+(bords[no].pos-at)*bords[nn].val)/(bords[no].pos-bords[nn].pos);
+// sum+=ValAt(at,bords[nn].pos,bords[no].pos,bords[nn].val,bords[no].val);
+ no=bords[no].next;
+ }*/
+ // for each portion being scanned, compute coverage at position "at" and sum.
+ // we could simply compute the sum of portion coverages as a "f(x)=ux+y" and evaluate it at "x=at",
+ // but there are numerical problems with this approach, and it produces ugly lines of incorrectly
+ // computed alpha values, so i reverted to this "safe but slow" version
+
+ for (int i=0; i < pending; i++) {
+ int const nn = bords[i].pend_ind;
+ sum += bords[nn].val + (at - bords[nn].pos) * bords[nn].pente;
+ }
+
+ return sum;
+}
+
+
+/**
+ * Extract a set of non-overlapping runs from the boundaries.
+ *
+ * We scan the boundaries left to right, maintaining a set of coverage
+ * portions currently being scanned. For each such portion, the function
+ * will add the index of its first boundary in an array; but instead of
+ * allocating another array, it uses a field in float_ligne_bord: pend_ind.
+ * The outcome is that an array of float_ligne_run is produced.
+ */
+void FloatLigne::Flatten()
+{
+ if ( int(bords.size()) <= 1 ) {
+ Reset();
+ return;
+ }
+
+ runs.clear();
+
+// qsort(bords,bords.size(),sizeof(float_ligne_bord),FloatLigne::CmpBord);
+// SortBords(0,bords.size()-1);
+
+ float totPente = 0;
+ float totStart = 0;
+ float totX = bords[0].pos;
+
+ bool startExists = false;
+ float lastStart = 0;
+ float lastVal = 0;
+ int pending = 0;
+
+// for (int i=0;i<bords.size();) {
+ // read the list from left to right, adding a run for each boundary crossed, minus runs with alpha=0
+ for (int i=/*0*/s_first; i>=0 && i < int(bords.size()) ;) {
+
+ float cur = bords[i].pos; // position of the current boundary (there may be several boundaries at this position)
+ float leftV = 0; // deltas in coverage value at this position
+ float rightV = 0;
+ float leftP = 0; // deltas in coverage increase per unit length at this position
+ float rightP = 0;
+
+ // more precisely, leftV is the sum of decreases of coverage value,
+ // while rightV is the sum of increases, so that leftV+rightV is the delta.
+ // idem for leftP and rightP
+
+ // start by scanning all boundaries that end a portion at this position
+ while ( i >= 0 && i < int(bords.size()) && bords[i].pos == cur && bords[i].start == false ) {
+ leftV += bords[i].val;
+ leftP += bords[i].pente;
+
+#ifndef faster_flatten
+ // we need to remove the boundary that started this coverage portion for the pending list
+ if ( bords[i].other >= 0 && bords[i].other < int(bords.size()) ) {
+ // so we use the pend_inv "array"
+ int const k = bords[bords[i].other].pend_inv;
+ if ( k >= 0 && k < pending ) {
+ // and update the pend_ind array and its inverse pend_inv
+ bords[k].pend_ind = bords[pending - 1].pend_ind;
+ bords[bords[k].pend_ind].pend_inv = k;
+ }
+ }
+#endif
+
+ // one less portion pending
+ pending--;
+ // and we move to the next boundary in the doubly linked list
+ i=bords[i].s_next;
+ //i++;
+ }
+
+ // then scan all boundaries that start a portion at this position
+ while ( i >= 0 && i < int(bords.size()) && bords[i].pos == cur && bords[i].start ) {
+ rightV += bords[i].val;
+ rightP += bords[i].pente;
+#ifndef faster_flatten
+ bords[pending].pend_ind=i;
+ bords[i].pend_inv=pending;
+#endif
+ pending++;
+ i = bords[i].s_next;
+ //i++;
+ }
+
+ // coverage value at end of the run will be "start coverage"+"delta per unit length"*"length"
+ totStart = totStart + totPente * (cur - totX);
+
+ if ( startExists ) {
+ // add that run
+ AddRun(lastStart, cur, lastVal, totStart, totPente);
+ }
+ // update "delta coverage per unit length"
+ totPente += rightP - leftP;
+ // not really needed here
+ totStart += rightV - leftV;
+ // update position
+ totX = cur;
+ if ( pending > 0 ) {
+ startExists = true;
+
+#ifndef faster_flatten
+ // to avoid accumulation of numerical errors, we compute an accurate coverage for this position "cur"
+ totStart = RemainingValAt(cur, pending);
+#endif
+ lastVal = totStart;
+ lastStart = cur;
+ } else {
+ startExists = false;
+ totStart = 0;
+ totPente = 0;
+ }
+ }
+}
+
+
+/// Debug dump of the instance.
+void FloatLigne::Affiche()
+{
+ printf("%lu : \n", (long unsigned int) bords.size());
+ for (auto & bord : bords) {
+ printf("(%f %f %f %i) ",bord.pos,bord.val,bord.pente,(bord.start?1:0)); // localization ok
+ }
+
+ printf("\n");
+ printf("%lu : \n", (long unsigned int) runs.size());
+
+ for (auto & run : runs) {
+ printf("(%f %f -> %f %f / %f)",
+ run.st, run.vst, run.en, run.ven, run.pente); // localization ok
+ }
+
+ printf("\n");
+}
+
+
+int FloatLigne::AddRun(float st, float en, float vst, float ven)
+{
+ return AddRun(st, en, vst, ven, (ven - vst) / (en - st));
+}
+
+
+int FloatLigne::AddRun(float st, float en, float vst, float ven, float pente)
+{
+ if ( st >= en ) {
+ return -1;
+ }
+
+ int const n = runs.size();
+ float_ligne_run r;
+ r.st = st;
+ r.en = en;
+ r.vst = vst;
+ r.ven = ven;
+ r.pente = pente;
+ runs.push_back(r);
+
+ return n;
+}
+
+void FloatLigne::Copy(FloatLigne *a)
+{
+ if ( a->runs.empty() ) {
+ Reset();
+ return;
+ }
+
+ bords.clear();
+ runs = a->runs;
+}
+
+void FloatLigne::Copy(IntLigne *a)
+{
+ if ( a->nbRun ) {
+ Reset();
+ return;
+ }
+
+ bords.clear();
+ runs.resize(a->nbRun);
+
+ for (int i = 0; i < int(runs.size()); i++) {
+ runs[i].st = a->runs[i].st;
+ runs[i].en = a->runs[i].en;
+ runs[i].vst = a->runs[i].vst;
+ runs[i].ven = a->runs[i].ven;
+ }
+}
+
+/// Cuts the parts having less than tresh coverage.
+void FloatLigne::Min(FloatLigne *a, float tresh, bool addIt)
+{
+ Reset();
+ if ( a->runs.empty() ) {
+ return;
+ }
+
+ bool startExists = false;
+ float lastStart=0;
+ float lastEnd = 0;
+
+ for (auto runA : a->runs) {
+ if ( runA.vst <= tresh ) {
+ if ( runA.ven <= tresh ) {
+ if ( startExists ) {
+ if ( lastEnd >= runA.st - 0.00001 ) {
+ lastEnd = runA.en;
+ } else {
+ if ( addIt ) {
+ AddRun(lastStart, lastEnd, tresh, tresh);
+ }
+ lastStart = runA.st;
+ lastEnd = runA.en;
+ }
+ } else {
+ lastStart = runA.st;
+ lastEnd = runA.en;
+ }
+ startExists = true;
+ } else {
+ float cutPos = (runA.st * (tresh - runA.ven) + runA.en * (runA.vst - tresh)) / (runA.vst - runA.ven);
+ if ( startExists ) {
+ if ( lastEnd >= runA.st - 0.00001 ) {
+ if ( addIt ) {
+ AddRun(lastStart, cutPos, tresh, tresh);
+ }
+ AddRun(cutPos,runA.en, tresh, runA.ven);
+ } else {
+ if ( addIt ) {
+ AddRun(lastStart, lastEnd, tresh, tresh);
+ }
+ if ( addIt ) {
+ AddRun(runA.st, cutPos, tresh, tresh);
+ }
+ AddRun(cutPos, runA.en, tresh, runA.ven);
+ }
+ } else {
+ if ( addIt ) {
+ AddRun(runA.st, cutPos, tresh, tresh);
+ }
+ AddRun(cutPos, runA.en, tresh, runA.ven);
+ }
+ startExists = false;
+ }
+
+ } else {
+
+ if ( runA.ven <= tresh ) {
+ float cutPos = (runA.st * (runA.ven - tresh) + runA.en * (tresh - runA.vst)) / (runA.ven - runA.vst);
+ if ( startExists ) {
+ if ( addIt ) {
+ AddRun(lastStart, lastEnd, tresh, tresh);
+ }
+ }
+ AddRun(runA.st, cutPos, runA.vst, tresh);
+ startExists = true;
+ lastStart = cutPos;
+ lastEnd = runA.en;
+ } else {
+ if ( startExists ) {
+ if ( addIt ) {
+ AddRun(lastStart, lastEnd, tresh, tresh);
+ }
+ }
+ startExists = false;
+ AddRun(runA.st, runA.en, runA.vst, runA.ven);
+ }
+ }
+ }
+
+ if ( startExists ) {
+ if ( addIt ) {
+ AddRun(lastStart, lastEnd, tresh, tresh);
+ }
+ }
+}
+
+/**
+ * Cuts the coverage a in 2 parts.
+ *
+ * over will receive the parts where coverage > tresh, while the present
+ * FloatLigne will receive the parts where coverage <= tresh.
+ */
+void FloatLigne::Split(FloatLigne *a, float tresh, FloatLigne *over)
+{
+ Reset();
+ if ( a->runs.empty() ) {
+ return;
+ }
+
+ for (auto runA : a->runs) {
+ if ( runA.vst >= tresh ) {
+ if ( runA.ven >= tresh ) {
+ if ( over ) {
+ over->AddRun(runA.st, runA.en, runA.vst, runA.ven);
+ }
+ } else {
+ float cutPos = (runA.st * (tresh - runA.ven) + runA.en * (runA.vst - tresh)) / (runA.vst - runA.ven);
+ if ( over ) {
+ over->AddRun(runA.st, cutPos, runA.vst, tresh);
+ }
+ AddRun(cutPos, runA.en, tresh, runA.ven);
+ }
+ } else {
+ if ( runA.ven >= tresh ) {
+ float cutPos = (runA.st * (runA.ven - tresh) + runA.en * (tresh-runA.vst)) / (runA.ven - runA.vst);
+ AddRun(runA.st, cutPos, runA.vst, tresh);
+ if ( over ) {
+ over->AddRun(cutPos, runA.en, tresh, runA.ven);
+ }
+ } else {
+ AddRun(runA.st, runA.en, runA.vst, runA.ven);
+ }
+ }
+ }
+}
+
+/**
+ * Clips the coverage runs to tresh.
+ *
+ * If addIt is false, it only leaves the parts that are not entirely under
+ * tresh. If addIt is true, it's the coverage clamped to tresh.
+ */
+void FloatLigne::Max(FloatLigne *a, float tresh, bool addIt)
+{
+ Reset();
+ if ( a->runs.empty() <= 0 ) {
+ return;
+ }
+
+ bool startExists = false;
+ float lastStart = 0;
+ float lastEnd = 0;
+ for (auto runA : a->runs) {
+ if ( runA.vst >= tresh ) {
+ if ( runA.ven >= tresh ) {
+ if ( startExists ) {
+ if ( lastEnd >= runA.st-0.00001 ) {
+ lastEnd = runA.en;
+ } else {
+ if ( addIt ) {
+ AddRun(lastStart,lastEnd,tresh,tresh);
+ }
+ lastStart = runA.st;
+ lastEnd = runA.en;
+ }
+ } else {
+ lastStart = runA.st;
+ lastEnd = runA.en;
+ }
+ startExists = true;
+ } else {
+ float cutPos = (runA.st * (tresh - runA.ven) + runA.en * (runA.vst - tresh)) / (runA.vst - runA.ven);
+ if ( startExists ) {
+ if ( lastEnd >= runA.st-0.00001 ) {
+ if ( addIt ) {
+ AddRun(lastStart, cutPos, tresh, tresh);
+ }
+ AddRun(cutPos, runA.en, tresh, runA.ven);
+ } else {
+ if ( addIt ) {
+ AddRun(lastStart, lastEnd, tresh, tresh);
+ }
+ if ( addIt ) {
+ AddRun(runA.st, cutPos, tresh, tresh);
+ }
+ AddRun(cutPos, runA.en, tresh, runA.ven);
+ }
+ } else {
+ if ( addIt ) {
+ AddRun(runA.st, cutPos, tresh, tresh);
+ }
+ AddRun(cutPos, runA.en, tresh, runA.ven);
+ }
+ startExists = false;
+ }
+
+ } else {
+
+ if ( runA.ven >= tresh ) {
+ float cutPos = (runA.st * (runA.ven - tresh) + runA.en * (tresh - runA.vst)) / (runA.ven - runA.vst);
+ if ( startExists ) {
+ if ( addIt ) {
+ AddRun(lastStart,lastEnd,tresh,tresh);
+ }
+ }
+ AddRun(runA.st, cutPos, runA.vst, tresh);
+ startExists = true;
+ lastStart = cutPos;
+ lastEnd = runA.en;
+ } else {
+ if ( startExists ) {
+ if ( addIt ) {
+ AddRun(lastStart,lastEnd,tresh,tresh);
+ }
+ }
+ startExists = false;
+ AddRun(runA.st, runA.en, runA.vst, runA.ven);
+ }
+ }
+ }
+
+ if ( startExists ) {
+ if ( addIt ) {
+ AddRun(lastStart, lastEnd, tresh, tresh);
+ }
+ }
+}
+
+/// Extract the parts where coverage > tresh.
+void FloatLigne::Over(FloatLigne *a, float tresh)
+{
+ Reset();
+ if ( a->runs.empty() ) {
+ return;
+ }
+
+ bool startExists = false;
+ float lastStart = 0;
+ float lastEnd = 0;
+
+ for (auto runA : a->runs) {
+ if ( runA.vst >= tresh ) {
+ if ( runA.ven >= tresh ) {
+ if ( startExists ) {
+ if ( lastEnd >= runA.st - 0.00001 ) {
+ lastEnd = runA.en;
+ } else {
+ AddRun(lastStart, lastEnd, tresh, tresh);
+ lastStart = runA.st;
+ lastEnd = runA.en;
+ }
+ } else {
+ lastStart = runA.st;
+ lastEnd = runA.en;
+ }
+ startExists = true;
+
+ } else {
+
+ float cutPos = (runA.st * (tresh - runA.ven) + runA.en * (runA.vst - tresh)) / (runA.vst - runA.ven);
+ if ( startExists ) {
+ if ( lastEnd >= runA.st - 0.00001 ) {
+ AddRun(lastStart, cutPos, tresh, tresh);
+ } else {
+ AddRun(lastStart, lastEnd, tresh, tresh);
+ AddRun(runA.st, cutPos, tresh, tresh);
+ }
+ } else {
+ AddRun(runA.st, cutPos, tresh, tresh);
+ }
+ startExists = false;
+ }
+
+ } else {
+ if ( runA.ven >= tresh ) {
+ float cutPos = (runA.st * (runA.ven - tresh) + runA.en * (tresh - runA.vst)) / (runA.ven - runA.vst);
+ if ( startExists ) {
+ AddRun(lastStart, lastEnd, tresh, tresh);
+ }
+ startExists = true;
+ lastStart = cutPos;
+ lastEnd = runA.en;
+ } else {
+ if ( startExists ) {
+ AddRun(lastStart, lastEnd, tresh, tresh);
+ }
+ startExists = false;
+ }
+ }
+ }
+
+ if ( startExists ) {
+ AddRun(lastStart, lastEnd, tresh, tresh);
+ }
+}
+
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/livarot/float-line.h b/src/livarot/float-line.h
new file mode 100644
index 0000000..ebbd9aa
--- /dev/null
+++ b/src/livarot/float-line.h
@@ -0,0 +1,144 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * TODO: insert short description here
+ *//*
+ * Authors: see git history
+ *
+ * Copyright (C) 2010 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+#ifndef INKSCAPE_LIVAROT_FLOAT_LINE_H
+#define INKSCAPE_LIVAROT_FLOAT_LINE_H
+
+/** \file
+ * Coverage with floating-point boundaries.
+ */
+
+#include <vector>
+#include "livarot/LivarotDefs.h"
+
+class IntLigne;
+
+/// A coverage portion ("run") with floating point boundaries.
+struct float_ligne_run {
+ float st;
+ float en;
+ float vst;
+ float ven;
+ float pente; ///< (ven-vst)/(en-st)
+};
+
+/**
+ * A floating-point boundary.
+ *
+ * Each float_ligne_bord is a boundary of some coverage.
+ * The Flatten() function will extract non-overlapping runs and produce an
+ * array of float_ligne_run. The float_ligne_bord are stored in an array, but
+ * linked like a doubly-linked list.
+ *
+ * The idea behind that is that a given edge produces one float_ligne_bord at
+ * the beginning of Scan() and possibly another in AvanceEdge() and
+ * DestroyEdge(); but that second float_ligne_bord will not be far away in
+ * the list from the first, so it's faster to salvage the index of the first
+ * float_ligne_bord and try to insert the second from that salvaged position.
+ */
+struct float_ligne_bord {
+ float pos; ///< position of the boundary
+ bool start; ///< is the beginning of the coverage portion?
+ float val; ///< amount of coverage (ie vst if start==true, and ven if start==false)
+ float pente; ///< (ven-vst)/(en-st)
+ int other; ///< index, in the array of float_ligne_bord, of the other boundary associated to this one
+ int s_prev; ///< index of the previous bord in the doubly-linked list
+ int s_next; ///< index of the next bord in the doubly-linked list
+ int pend_ind; ///< bords[i].pend_ind is the index of the float_ligne_bord that is the start of the
+ ///< coverage portion being scanned (in the Flatten() )
+ int pend_inv; ///< inverse of pend_ind, for faster handling of insertion/removal in the "pending" array
+};
+
+/**
+ * Coverage with floating-point boundaries.
+ *
+ * The goal is to salvage exact coverage info in the sweepline performed by
+ * Scan() or QuickScan(), then clean up a bit, convert floating point bounds
+ * to integer bounds, because pixel have integer bounds, and then raster runs
+ * of the type:
+ * \verbatim
+ position on the (pixel) line: st en
+ | |
+ coverage value (0=empty, 1=full) vst -> ven \endverbatim
+ */
+class FloatLigne {
+public:
+ std::vector<float_ligne_bord> bords; ///< vector of coverage boundaries
+ std::vector<float_ligne_run> runs; ///< vector of runs
+
+ /// first boundary in the doubly-linked list
+ int s_first;
+ /// last boundary in the doubly-linked list
+ int s_last;
+
+ FloatLigne();
+ virtual ~FloatLigne();
+
+ void Reset();
+
+ int AddBord(float spos, float sval, float epos, float eval, int guess = -1);
+ int AddBord(float spos, float sval, float epos, float eval, float pente, int guess = -1);
+ int AddBordR(float spos, float sval, float epos, float eval, float pente, int guess = -1);
+ int AppendBord(float spos, float sval, float epos, float eval, float pente);
+
+ void Flatten();
+
+ void Affiche();
+
+ void Max(FloatLigne *a, float tresh, bool addIt);
+
+ void Min(FloatLigne *a, float tresh, bool addIt);
+
+ void Split(FloatLigne *a, float tresh, FloatLigne *over);
+
+ void Over(FloatLigne *a, float tresh);
+
+ void Copy(IntLigne *a);
+ void Copy(FloatLigne *a);
+
+ float RemainingValAt(float at, int pending);
+
+ static int CmpBord(float_ligne_bord const &d1, float_ligne_bord const &d2) {
+ if ( d1.pos == d2.pos ) {
+ if ( d1.start && !(d2.start) ) {
+ return 1;
+ }
+ if ( !(d1.start) && d2.start ) {
+ return -1;
+ }
+ return 0;
+ }
+
+ return (( d1.pos < d2.pos ) ? -1 : 1);
+ };
+
+ int AddRun(float st, float en, float vst, float ven, float pente);
+
+private:
+ void InsertBord(int no, float p, int guess);
+ int AddRun(float st, float en, float vst, float ven);
+
+ inline float ValAt(float at, float ps, float pe, float vs, float ve) {
+ return ((at - ps) * ve + (pe - at) * vs) / (pe - ps);
+ };
+};
+
+#endif
+
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/livarot/int-line.cpp b/src/livarot/int-line.cpp
new file mode 100644
index 0000000..ff87475
--- /dev/null
+++ b/src/livarot/int-line.cpp
@@ -0,0 +1,1071 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * Implementation of coverage with integer boundaries.
+ *//*
+ * Authors:
+ * see git history
+ * Fred
+ *
+ * Copyright (C) 2018 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <glib.h>
+#include <cmath>
+#include <cstring>
+#include <string>
+#include <cstdlib>
+#include <cstdio>
+#include "livarot/int-line.h"
+#include "livarot/float-line.h"
+#include "livarot/BitLigne.h"
+
+IntLigne::IntLigne()
+{
+ nbBord = maxBord = 0;
+ bords = nullptr;
+
+ nbRun = maxRun = 0;
+ runs = nullptr;
+
+ firstAc = lastAc = -1;
+}
+
+
+IntLigne::~IntLigne()
+{
+ if ( maxBord > 0 ) {
+ g_free(bords);
+ nbBord = maxBord = 0;
+ bords = nullptr;
+ }
+ if ( maxRun > 0 ) {
+ g_free(runs);
+ nbRun = maxRun = 0;
+ runs = nullptr;
+ }
+}
+
+void IntLigne::Reset()
+{
+ nbBord = 0;
+ nbRun = 0;
+ firstAc = lastAc = -1;
+}
+
+int IntLigne::AddBord(int spos, float sval, int epos, float eval)
+{
+ if ( nbBord + 1 >= maxBord ) {
+ maxBord = 2 * nbBord + 2;
+ bords = (int_ligne_bord *) g_realloc(bords, maxBord * sizeof(int_ligne_bord));
+
+ }
+
+ int n = nbBord++;
+ bords[n].pos = spos;
+ bords[n].val = sval;
+ bords[n].start = true;
+ bords[n].other = n+1;
+ bords[n].prev = bords[n].next = -1;
+
+ n = nbBord++;
+ bords[n].pos = epos;
+ bords[n].val = eval;
+ bords[n].start = false;
+ bords[n].other = n-1;
+ bords[n].prev = bords[n].next = -1;
+
+ return n - 1;
+}
+
+
+float IntLigne::RemainingValAt(int at)
+{
+ int no = firstAc;
+ float sum = 0;
+ while ( no >= 0 ) {
+ int nn = bords[no].other;
+ sum += ValAt(at, bords[nn].pos, bords[no].pos, bords[nn].val, bords[no].val);
+ no = bords[no].next;
+ }
+ return sum;
+}
+
+void IntLigne::Flatten()
+{
+ if ( nbBord <= 1 ) {
+ Reset();
+ return;
+ }
+
+ nbRun = 0;
+ firstAc = lastAc = -1;
+
+ for (int i = 0; i < nbBord; i++) {
+ bords[i].prev = i;
+ }
+
+ qsort(bords, nbBord, sizeof(int_ligne_bord), IntLigne::CmpBord);
+ for (int i = 0; i < nbBord; i++) {
+ bords[bords[i].prev].next = i;
+ }
+
+ for (int i = 0; i < nbBord; i++) {
+ bords[i].other = bords[bords[i].other].next;
+ }
+
+ int lastStart = 0;
+ float lastVal = 0;
+ bool startExists = false;
+
+ for (int i = 0; i < nbBord; ) {
+ int cur = bords[i].pos;
+ float leftV = 0;
+ float rightV = 0;
+ float midV = 0;
+ while ( i < nbBord && bords[i].pos == cur && bords[i].start == false ) {
+ Dequeue(i);
+ leftV += bords[i].val;
+ i++;
+ }
+ midV = RemainingValAt(cur);
+ while ( i < nbBord && bords[i].pos == cur && bords[i].start ) {
+ rightV += bords[i].val;
+ Enqueue(bords[i].other);
+ i++;
+ }
+
+ if ( startExists ) {
+ AddRun(lastStart, cur, lastVal, leftV + midV);
+ }
+ if ( firstAc >= 0 ) {
+ startExists = true;
+ lastVal = midV + rightV;
+ lastStart = cur;
+ } else {
+ startExists = false;
+ }
+ }
+}
+
+
+void IntLigne::Affiche()
+{
+ printf("%i : \n", nbRun);
+ for (int i = 0; i < nbRun;i++) {
+ printf("(%i %f -> %i %f) ", runs[i].st, runs[i].vst, runs[i].en, runs[i].ven); // localization ok
+ }
+ printf("\n");
+}
+
+int IntLigne::AddRun(int st, int en, float vst, float ven)
+{
+ if ( st >= en ) {
+ return -1;
+ }
+
+ if ( nbRun >= maxRun ) {
+ maxRun = 2 * nbRun + 1;
+ runs = (int_ligne_run *) g_realloc(runs, maxRun * sizeof(int_ligne_run));
+ }
+
+ int n = nbRun++;
+ runs[n].st = st;
+ runs[n].en = en;
+ runs[n].vst = vst;
+ runs[n].ven = ven;
+ return n;
+}
+
+void IntLigne::Booleen(IntLigne *a, IntLigne *b, BooleanOp mod)
+{
+ Reset();
+ if ( a->nbRun <= 0 && b->nbRun <= 0 ) {
+ return;
+ }
+
+ if ( a->nbRun <= 0 ) {
+ if ( mod == bool_op_union || mod == bool_op_symdiff ) {
+ Copy(b);
+ }
+ return;
+ }
+
+ if ( b->nbRun <= 0 ) {
+ if ( mod == bool_op_union || mod == bool_op_diff || mod == bool_op_symdiff ) {
+ Copy(a);
+ }
+ return;
+ }
+
+ int curA = 0;
+ int curB = 0;
+ int curPos = (a->runs[0].st < b->runs[0].st) ? a->runs[0].st : b->runs[0].st;
+ int nextPos = curPos;
+ float valA = 0;
+ float valB = 0;
+ if ( curPos == a->runs[0].st ) {
+ valA = a->runs[0].vst;
+ }
+ if ( curPos == b->runs[0].st ) {
+ valB = b->runs[0].vst;
+ }
+
+ while ( curA < a->nbRun && curB < b->nbRun ) {
+ int_ligne_run runA = a->runs[curA];
+ int_ligne_run runB = b->runs[curB];
+ const bool inA = ( curPos >= runA.st && curPos < runA.en );
+ const bool inB = ( curPos >= runB.st && curPos < runB.en );
+
+ bool startA = false;
+ bool startB = false;
+ bool endA = false;
+ bool endB = false;
+
+ if ( curPos < runA.st ) {
+ if ( curPos < runB.st ) {
+ startA = runA.st <= runB.st;
+ startB = runA.st >= runB.st;
+ nextPos = startA ? runA.st : runB.st;
+ } else if ( curPos >= runB.st ) {
+ startA = runA.st <= runB.en;
+ endB = runA.st >= runB.en;
+ nextPos = startA ? runA.st : runB.en;
+ }
+ } else if ( curPos == runA.st ) {
+ if ( curPos < runB.st ) {
+ endA = runA.en <= runB.st;
+ startB = runA.en >= runB.st;
+ nextPos = startB ? runB.en : runA.st;
+ } else if ( curPos == runB.st ) {
+ endA = runA.en <= runB.en;
+ endB = runA.en >= runB.en;
+ nextPos = endA? runA.en : runB.en;
+ } else {
+ endA = runA.en <= runB.en;
+ endB = runA.en >= runB.en;
+ nextPos = endA ? runA.en : runB.en;
+ }
+ } else {
+ if ( curPos < runB.st ) {
+ endA = runA.en <= runB.st;
+ startB = runA.en >= runB.st;
+ nextPos = startB ? runB.st : runA.en;
+ } else if ( curPos == runB.st ) {
+ endA = runA.en <= runB.en;
+ endB = runA.en >= runB.en;
+ nextPos = endA ? runA.en : runB.en;
+ } else {
+ endA = runA.en <= runB.en;
+ endB = runA.en >= runB.en;
+ nextPos = endA ? runA.en : runB.en;
+ }
+ }
+
+ float oValA = valA;
+ float oValB = valB;
+ valA = inA ? ValAt(nextPos, runA.st, runA.en, runA.vst, runA.ven) : 0;
+ valB = inB ? ValAt(nextPos, runB.st, runB.en, runB.vst, runB.ven) : 0;
+
+ if ( mod == bool_op_union ) {
+
+ if ( inA || inB ) {
+ AddRun(curPos, nextPos, oValA + oValB, valA + valB);
+ }
+
+ } else if ( mod == bool_op_inters ) {
+
+ if ( inA && inB ) {
+ AddRun(curPos, nextPos, oValA * oValB, valA * valB);
+ }
+
+ } else if ( mod == bool_op_diff ) {
+
+ if ( inA ) {
+ AddRun(curPos, nextPos, oValA - oValB, valA - valB);
+ }
+
+ } else if ( mod == bool_op_symdiff ) {
+ if ( inA && !(inB) ) {
+ AddRun(curPos, nextPos, oValA - oValB, valA - valB);
+ }
+ if ( !(inA) && inB ) {
+ AddRun(curPos, nextPos, oValB - oValA, valB - valA);
+ }
+ }
+
+ curPos = nextPos;
+ if ( startA ) {
+ // inA=true; these are never used
+ valA = runA.vst;
+ }
+ if ( startB ) {
+ //inB=true;
+ valB = runB.vst;
+ }
+ if ( endA ) {
+ //inA=false;
+ valA = 0;
+ curA++;
+ if ( curA < a->nbRun && a->runs[curA].st == curPos ) {
+ valA = a->runs[curA].vst;
+ }
+ }
+ if ( endB ) {
+ //inB=false;
+ valB = 0;
+ curB++;
+ if ( curB < b->nbRun && b->runs[curB].st == curPos ) {
+ valB = b->runs[curB].vst;
+ }
+ }
+ }
+
+ while ( curA < a->nbRun ) {
+ int_ligne_run runA = a->runs[curA];
+ const bool inA = ( curPos >= runA.st && curPos < runA.en );
+ const bool inB = false;
+
+ bool startA = false;
+ bool endA = false;
+ if ( curPos < runA.st ) {
+ nextPos = runA.st;
+ startA = true;
+ } else if ( curPos >= runA.st ) {
+ nextPos = runA.en;
+ endA = true;
+ }
+
+ float oValA = valA;
+ float oValB = valB;
+ valA = inA ? ValAt(nextPos,runA.st, runA.en, runA.vst, runA.ven) : 0;
+ valB = 0;
+
+ if ( mod == bool_op_union ) {
+ if ( inA || inB ) {
+ AddRun(curPos, nextPos, oValA + oValB, valA + valB);
+ }
+ } else if ( mod == bool_op_inters ) {
+ if ( inA && inB ) {
+ AddRun(curPos, nextPos, oValA * oValB, valA * valB);
+ }
+ } else if ( mod == bool_op_diff ) {
+ if ( inA ) {
+ AddRun(curPos, nextPos, oValA - oValB, valA - valB);
+ }
+ } else if ( mod == bool_op_symdiff ) {
+ if ( inA && !(inB) ) {
+ AddRun(curPos, nextPos, oValA - oValB, valA - valB);
+ }
+ if ( !(inA) && inB ) {
+ AddRun(curPos,nextPos,oValB-oValA,valB-valA);
+ }
+ }
+
+ curPos = nextPos;
+ if ( startA ) {
+ //inA=true;
+ valA = runA.vst;
+ }
+ if ( endA ) {
+ //inA=false;
+ valA = 0;
+ curA++;
+ if ( curA < a->nbRun && a->runs[curA].st == curPos ) {
+ valA = a->runs[curA].vst;
+ }
+ }
+ }
+
+ while ( curB < b->nbRun ) {
+ int_ligne_run runB = b->runs[curB];
+ const bool inB = ( curPos >= runB.st && curPos < runB.en );
+ const bool inA = false;
+
+ bool startB = false;
+ bool endB = false;
+ if ( curPos < runB.st ) {
+ nextPos = runB.st;
+ startB = true;
+ } else if ( curPos >= runB.st ) {
+ nextPos = runB.en;
+ endB = true;
+ }
+
+ float oValA = valA;
+ float oValB = valB;
+ valB = inB ? ValAt(nextPos, runB.st, runB.en, runB.vst, runB.ven) : 0;
+ valA = 0;
+
+ if ( mod == bool_op_union ) {
+ if ( inA || inB ) {
+ AddRun(curPos, nextPos, oValA + oValB,valA + valB);
+ }
+ } else if ( mod == bool_op_inters ) {
+ if ( inA && inB ) {
+ AddRun(curPos, nextPos, oValA * oValB, valA * valB);
+ }
+ } else if ( mod == bool_op_diff ) {
+ if ( inA ) {
+ AddRun(curPos, nextPos, oValA - oValB, valA - valB);
+ }
+ } else if ( mod == bool_op_symdiff ) {
+ if ( inA && !(inB) ) {
+ AddRun(curPos, nextPos, oValA - oValB,valA - valB);
+ }
+ if ( !(inA) && inB ) {
+ AddRun(curPos, nextPos, oValB - oValA, valB - valA);
+ }
+ }
+
+ curPos = nextPos;
+ if ( startB ) {
+ //inB=true;
+ valB = runB.vst;
+ }
+ if ( endB ) {
+ //inB=false;
+ valB = 0;
+ curB++;
+ if ( curB < b->nbRun && b->runs[curB].st == curPos ) {
+ valB = b->runs[curB].vst;
+ }
+ }
+ }
+}
+
+/**
+ * Transform a line of bits into pixel coverage values.
+ *
+ * This is where you go from supersampled data to alpha values.
+ * \see IntLigne::Copy(int nbSub,BitLigne* *a).
+ */
+void IntLigne::Copy(BitLigne* a)
+{
+ if ( a->curMax <= a->curMin ) {
+ Reset();
+ return;
+ }
+
+ if ( a->curMin < a->st ) {
+ a->curMin = a->st;
+ }
+
+ if ( a->curMax < a->st ) {
+ Reset();
+ return;
+ }
+
+ if ( a->curMin > a->en ) {
+ Reset();
+ return;
+ }
+
+ if ( a->curMax > a->en ) {
+ a->curMax=a->en;
+ }
+
+ nbBord = 0;
+ nbRun = 0;
+
+ int lastVal = 0;
+ int lastStart = 0;
+ bool startExists = false;
+
+ int masks[] = { 0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4 };
+
+ uint32_t c_full = a->fullB[(a->curMin-a->st) >> 3];
+ uint32_t c_part = a->partB[(a->curMin-a->st) >> 3];
+ c_full <<= 4 * ((a->curMin - a->st) & 0x00000007);
+ c_part <<= 4 * ((a->curMin - a->st) & 0x00000007);
+ for (int i = a->curMin; i <= a->curMax; i++) {
+ int nbBit = masks[c_full >> 28] + masks[c_part >> 28];
+
+ if ( nbBit > 0 ) {
+ if ( startExists ) {
+ if ( lastVal == nbBit ) {
+ // on continue le run
+ } else {
+ AddRun(lastStart, i, ((float) lastVal) / 4, ((float) lastVal) / 4);
+ lastStart = i;
+ lastVal = nbBit;
+ }
+ } else {
+ lastStart = i;
+ lastVal = nbBit;
+ startExists = true;
+ }
+ } else {
+ if ( startExists ) {
+ AddRun(lastStart, i, ((float) lastVal) / 4, ((float) lastVal) / 4);
+ }
+ startExists = false;
+ }
+ int chg = (i + 1 - a->st) & 0x00000007;
+ if ( chg == 0 ) {
+ c_full = a->fullB[(i + 1 - a->st) >> 3];
+ c_part = a->partB[(i + 1 - a->st) >> 3];
+ } else {
+ c_full <<= 4;
+ c_part <<= 4;
+ }
+ }
+ if ( startExists ) {
+ AddRun(lastStart, a->curMax + 1, ((float) lastVal) / 4, ((float) lastVal) / 4);
+ }
+}
+
+/**
+ * Transform a line of bits into pixel coverage values.
+ *
+ * Alpha values are computed from supersampled data, so we have to scan the
+ * BitLigne left to right, summing the bits in each pixel. The alpha value
+ * is then "number of bits"/(nbSub*nbSub)". Full bits and partial bits are
+ * treated as equals because the method produces ugly results otherwise.
+ *
+ * \param nbSub Number of BitLigne in the array "a".
+ */
+void IntLigne::Copy(int nbSub, BitLigne **as)
+{
+ if ( nbSub <= 0 ) {
+ Reset();
+ return;
+ }
+
+ if ( nbSub == 1 ) {
+ Copy(as[0]);
+ return;
+ }
+
+ // compute the min-max of the pixels to be rasterized from the min-max of the inpur bitlignes
+ int curMin = as[0]->curMin;
+ int curMax = as[0]->curMax;
+ for (int i = 1; i < nbSub; i++) {
+ if ( as[i]->curMin < curMin ) {
+ curMin = as[i]->curMin;
+ }
+ if ( as[i]->curMax > curMax ) {
+ curMax = as[i]->curMax;
+ }
+ }
+
+ if ( curMin < as[0]->st ) {
+ curMin = as[0]->st;
+ }
+
+ if ( curMax > as[0]->en ) {
+ curMax = as[0]->en;
+ }
+
+ if ( curMax <= curMin ) {
+ Reset();
+ return;
+ }
+
+ nbBord = 0;
+ nbRun = 0;
+
+ int lastVal = 0;
+ int lastStart = 0;
+ bool startExists = false;
+ float spA;
+ int masks[16] = { 0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4};
+
+ int theSt = as[0]->st;
+ if ( nbSub == 4 ) {
+ // special case for 4*4 supersampling, to avoid a few loops
+ uint32_t c_full[4];
+ c_full[0] = as[0]->fullB[(curMin - theSt) >> 3] | as[0]->partB[(curMin - theSt) >> 3];
+ c_full[0] <<= 4 * ((curMin - theSt) & 7);
+ c_full[1] = as[1]->fullB[(curMin - theSt) >> 3] | as[1]->partB[(curMin - theSt) >> 3];
+ c_full[1] <<= 4 * ((curMin - theSt) & 7);
+ c_full[2] = as[2]->fullB[(curMin - theSt) >> 3] | as[2]->partB[(curMin - theSt) >> 3];
+ c_full[2] <<= 4* ((curMin - theSt) & 7);
+ c_full[3] = as[3]->fullB[(curMin - theSt) >> 3] | as[3]->partB[(curMin - theSt) >> 3];
+ c_full[3] <<= 4* ((curMin - theSt) & 7);
+
+ spA = 1.0 / (4 * 4);
+ for (int i = curMin; i <= curMax; i++) {
+ int nbBit = 0;
+
+ if ( c_full[0] == 0 && c_full[1] == 0 && c_full[2] == 0 && c_full[3] == 0 ) {
+
+ if ( startExists ) {
+ AddRun(lastStart, i, ((float) lastVal) * spA, ((float) lastVal) * spA);
+ }
+ startExists = false;
+ i = theSt + (((i - theSt) & (~7) ) + 7);
+
+ } else if ( c_full[0] == 0xFFFFFFFF && c_full[1] == 0xFFFFFFFF &&
+ c_full[2] == 0xFFFFFFFF && c_full[3] == 0xFFFFFFFF ) {
+
+ if ( startExists ) {
+ if ( lastVal == 4*4) {
+ } else {
+ AddRun(lastStart, i, ((float) lastVal) * spA, ((float) lastVal) * spA);
+ lastStart = i;
+ }
+ } else {
+ lastStart = i;
+ }
+ lastVal = 4*4;
+ startExists = true;
+ i = theSt + (((i - theSt) & (~7) ) + 7);
+
+ } else {
+ nbBit += masks[c_full[0] >> 28];
+ nbBit += masks[c_full[1] >> 28];
+ nbBit += masks[c_full[2] >> 28];
+ nbBit += masks[c_full[3] >> 28];
+
+ if ( nbBit > 0 ) {
+ if ( startExists ) {
+ if ( lastVal == nbBit ) {
+ // on continue le run
+ } else {
+ AddRun(lastStart, i, ((float) lastVal) * spA, ((float) lastVal) * spA);
+ lastStart = i;
+ lastVal = nbBit;
+ }
+ } else {
+ lastStart = i;
+ lastVal = nbBit;
+ startExists = true;
+ }
+ } else {
+ if ( startExists ) {
+ AddRun(lastStart, i, ((float) lastVal) * spA, ((float) lastVal) * spA);
+ }
+ startExists = false;
+ }
+ }
+ int chg = (i + 1 - theSt) & 7;
+ if ( chg == 0 ) {
+ if ( i < curMax ) {
+ c_full[0] = as[0]->fullB[(i + 1 - theSt) >> 3] | as[0]->partB[(i + 1 - theSt) >> 3];
+ c_full[1] = as[1]->fullB[(i + 1 - theSt) >> 3] | as[1]->partB[(i + 1 - theSt) >> 3];
+ c_full[2] = as[2]->fullB[(i + 1 - theSt) >> 3] | as[2]->partB[(i + 1 - theSt) >> 3];
+ c_full[3] = as[3]->fullB[(i + 1 - theSt) >> 3] | as[3]->partB[(i + 1 - theSt) >> 3];
+ } else {
+ // end of line. byebye
+ }
+ } else {
+ c_full[0] <<= 4;
+ c_full[1] <<= 4;
+ c_full[2] <<= 4;
+ c_full[3] <<= 4;
+ }
+ }
+
+ } else {
+
+ uint32_t c_full[16]; // we take nbSub < 16, since 16*16 supersampling makes a 1/256 precision in alpha values
+ // and that's the max of what 32bit argb can represent
+ // in fact, we'll treat it as 4*nbSub supersampling, so that's a half truth and a full lazyness from me
+ // uint32_t c_part[16];
+ // start by putting the bits of the nbSub BitLignes in as[] in their respective c_full
+
+ for (int i = 0; i < nbSub; i++) {
+ // fullB and partB treated equally
+ c_full[i] = as[i]->fullB[(curMin - theSt) >> 3] | as[i]->partB[(curMin - theSt) >> 3];
+ c_full[i] <<= 4 * ((curMin - theSt) & 7);
+ /* c_part[i]=as[i]->partB[(curMin-theSt)>>3];
+ c_part[i]<<=4*((curMin-theSt)&7);*/
+ }
+
+ spA = 1.0 / (4 * nbSub); // contribution to the alpha value of a single bit of the supersampled data
+ for (int i = curMin; i <= curMax;i++) {
+ int nbBit = 0;
+ // int nbPartBit=0;
+ // a little acceleration: if the lines only contain full or empty bits, we can flush
+ // what's remaining in the c_full at best we flush an entire c_full, ie 32 bits, or 32/4=8 pixels
+ bool allEmpty = true;
+ bool allFull = true;
+ for (int j = 0; j < nbSub; j++) {
+ if ( c_full[j] != 0 /*|| c_part[j] != 0*/ ) {
+ allEmpty=false;
+ break;
+ }
+ }
+
+ if ( allEmpty ) {
+ // the remaining bits in c_full[] are empty: flush
+ if ( startExists ) {
+ AddRun(lastStart, i, ((float) lastVal) * spA, ((float) lastVal) * spA);
+ }
+ startExists = false;
+ i = theSt + (((i - theSt) & (~7) ) + 7);
+ } else {
+ for (int j = 0; j < nbSub; j++) {
+ if ( c_full[j] != 0xFFFFFFFF ) {
+ allFull=false;
+ break;
+ }
+ }
+
+ if ( allFull ) {
+ // the remaining bits in c_full[] are empty: flush
+ if ( startExists ) {
+ if ( lastVal == 4 * nbSub) {
+ } else {
+ AddRun(lastStart, i, ((float) lastVal) * spA,((float) lastVal) * spA);
+ lastStart = i;
+ }
+ } else {
+ lastStart = i;
+ }
+ lastVal = 4 * nbSub;
+ startExists = true;
+ i = theSt + (((i - theSt) & (~7) ) + 7);
+ } else {
+ // alpha values will be between 0 and 1, so we have more work to do
+ // compute how many bit this pixel holds
+ for (int j = 0; j < nbSub; j++) {
+ nbBit += masks[c_full[j] >> 28];
+// nbPartBit+=masks[c_part[j]>>28];
+ }
+ // and add a single-pixel run if needed, or extend the current run if the alpha value hasn't changed
+ if ( nbBit > 0 ) {
+ if ( startExists ) {
+ if ( lastVal == nbBit ) {
+ // alpha value hasn't changed: we continue
+ } else {
+ // alpha value did change: put the run that was being done,...
+ AddRun(lastStart, i, ((float) lastVal) * spA, ((float) lastVal) * spA);
+ // ... and start a new one
+ lastStart = i;
+ lastVal = nbBit;
+ }
+ } else {
+ // alpha value was 0, so we "create" a new run with alpha nbBit
+ lastStart = i;
+ lastVal = nbBit;
+ startExists = true;
+ }
+ } else {
+ if ( startExists ) {
+ AddRun(lastStart, i, ((float) lastVal) * spA,((float) lastVal) * spA);
+ }
+ startExists = false;
+ }
+ }
+ }
+ // move to the right: shift bits in the c_full[], and if we shifted everything, load the next c_full[]
+ int chg = (i + 1 - theSt) & 7;
+ if ( chg == 0 ) {
+ if ( i < curMax ) {
+ for (int j = 0; j < nbSub; j++) {
+ c_full[j] = as[j]->fullB[(i + 1 - theSt) >> 3] | as[j]->partB[(i + 1 - theSt) >> 3];
+ // c_part[j]=as[j]->partB[(i+1-theSt)>>3];
+ }
+ } else {
+ // end of line. byebye
+ }
+ } else {
+ for (int j = 0; j < nbSub; j++) {
+ c_full[j]<<=4;
+ // c_part[j]<<=4;
+ }
+ }
+ }
+ }
+
+ if ( startExists ) {
+ AddRun(lastStart, curMax + 1, ((float) lastVal) * spA,((float) lastVal) * spA);
+ }
+}
+
+/// Copy another IntLigne
+void IntLigne::Copy(IntLigne *a)
+{
+ if ( a->nbRun <= 0 ) {
+ Reset();
+ return;
+ }
+
+ nbBord = 0;
+ nbRun = a->nbRun;
+ if ( nbRun > maxRun ) {
+ maxRun = nbRun;
+ runs = (int_ligne_run*) g_realloc(runs, maxRun * sizeof(int_ligne_run));
+ }
+ memcpy(runs, a->runs, nbRun * sizeof(int_ligne_run));
+}
+
+
+/**
+ * Copy a FloatLigne's runs.
+ *
+ * Compute non-overlapping runs with integer boundaries from a set of runs
+ * with floating-point boundaries. This involves replacing floating-point
+ * boundaries that are not integer by single-pixel runs, so this function
+ * contains plenty of rounding and float->integer conversion (read:
+ * time-consuming).
+ *
+ * \todo
+ * Optimization Questions: Why is this called so often compared with the
+ * other Copy() routines? How does AddRun() look for optimization potential?
+ */
+void IntLigne::Copy(FloatLigne* a)
+{
+ if ( a->runs.empty() ) {
+ Reset();
+ return;
+ }
+
+ /* if ( showCopy ) {
+ printf("\nfloatligne:\n");
+ a->Affiche();
+ }*/
+
+ nbBord = 0;
+ nbRun = 0;
+ firstAc = lastAc = -1;
+ bool pixExists = false;
+ int curPos = (int) floor(a->runs[0].st) - 1;
+ float lastSurf = 0;
+ float tolerance = 0.00001;
+
+ // we take each run of the FloatLigne in sequence and make single-pixel runs of its boundaries as needed
+ // since the float_ligne_runs are non-overlapping, when a single-pixel run intersects with another runs,
+ // it must intersect with the single-pixel run created for the end of that run. so instead of creating a new
+ // int_ligne_run, we just add the coverage to that run.
+ for (auto & run : a->runs) {
+ float_ligne_run runA = run;
+ float curStF = floor(runA.st);
+ float curEnF = floor(runA.en);
+ int curSt = (int) curStF;
+ int curEn = (int) curEnF;
+
+ // stEx: start boundary is not integer -> create single-pixel run for it
+ // enEx: end boundary is not integer -> create single-pixel run for it
+ // miEx: the runs minus the eventual single-pixel runs is not empty
+ bool stEx = true;
+ bool miEx = true;
+ bool enEx = true;
+ int miSt = curSt;
+ float miStF = curStF;
+ float msv;
+ float mev;
+ if ( runA.en - curEnF < tolerance ) {
+ enEx = false;
+ }
+
+ // msv and mev are the start and end value of the middle section of the run, that is the run minus the
+ // single-pixel runs creaed for its boundaries
+ if ( runA.st-curStF < tolerance /*miSt == runA.st*/ ) {
+ stEx = false;
+ msv = runA.vst;
+ } else {
+ miSt += 1;
+ miStF += 1.0;
+ if ( enEx == false && miSt == curEn ) {
+ msv = runA.ven;
+ } else {
+ // msv=a->ValAt(miSt,runA.st,runA.en,runA.vst,runA.ven);
+ msv = runA.vst + (miStF-runA.st) * runA.pente;
+ }
+ }
+
+ if ( miSt >= curEn ) {
+ miEx = false;
+ }
+ if ( stEx == false && miEx == false /*curEn == runA.st*/ ) {
+ mev = runA.vst;
+ } else if ( enEx == false /*curEn == runA.en*/ ) {
+ mev = runA.ven;
+ } else {
+ // mev=a->ValAt(curEn,runA.st,runA.en,runA.vst,runA.ven);
+ mev = runA.vst + (curEnF-runA.st) * runA.pente;
+ }
+
+ // check the different cases
+ if ( stEx && enEx ) {
+ // stEx && enEx
+ if ( curEn > curSt ) {
+ if ( pixExists ) {
+ if ( curPos < curSt ) {
+ AddRun(curPos,curPos+1,lastSurf,lastSurf);
+ lastSurf=0.5*(msv+run.vst)*(miStF-run.st);
+ AddRun(curSt,curSt+1,lastSurf,lastSurf);
+ } else {
+ lastSurf+=0.5*(msv+run.vst)*(miStF-run.st);
+ AddRun(curSt,curSt+1,lastSurf,lastSurf);
+ }
+ pixExists=false;
+ } else {
+ lastSurf=0.5*(msv+run.vst)*(miStF-run.st);
+ AddRun(curSt,curSt+1,lastSurf,lastSurf);
+ }
+ } else if ( pixExists ) {
+ if ( curPos < curSt ) {
+ AddRun(curPos,curPos+1,lastSurf,lastSurf);
+ lastSurf=0.5*(run.ven+run.vst)*(run.en-run.st);
+ curPos=curSt;
+ } else {
+ lastSurf += 0.5 * (run.ven+run.vst)*(run.en-run.st);
+ }
+ } else {
+ lastSurf=0.5*(run.ven+run.vst)*(run.en-run.st);
+ curPos=curSt;
+ pixExists=true;
+ }
+ } else if ( pixExists ) {
+ if ( curPos < curSt ) {
+ AddRun(curPos,curPos+1,lastSurf,lastSurf);
+ lastSurf = 0.5 * (msv+run.vst) * (miStF-run.st);
+ AddRun(curSt,curSt+1,lastSurf,lastSurf);
+ } else {
+ lastSurf += 0.5 * (msv+run.vst) * (miStF-run.st);
+ AddRun(curSt,curSt+1,lastSurf,lastSurf);
+ }
+ pixExists=false;
+ } else {
+ lastSurf = 0.5 * (msv+run.vst) * (miStF-run.st);
+ AddRun(curSt,curSt+1,lastSurf,lastSurf);
+ }
+ if ( miEx ) {
+ if ( pixExists && curPos < miSt ) {
+ AddRun(curPos,curPos+1,lastSurf,lastSurf);
+ }
+ pixExists=false;
+ AddRun(miSt,curEn,msv,mev);
+ }
+ if ( enEx ) {
+ if ( curEn > curSt ) {
+ lastSurf=0.5*(mev+run.ven)*(run.en-curEnF);
+ pixExists=true;
+ curPos=curEn;
+ } else if ( ! stEx ) {
+ if ( pixExists ) {
+ AddRun(curPos,curPos+1,lastSurf,lastSurf);
+ }
+ lastSurf=0.5*(mev+run.ven)*(run.en-curEnF);
+ pixExists=true;
+ curPos=curEn;
+ }
+ }
+ }
+ if ( pixExists ) {
+ AddRun(curPos,curPos+1,lastSurf,lastSurf);
+ }
+ /* if ( showCopy ) {
+ printf("-> intligne:\n");
+ Affiche();
+ }*/
+}
+
+
+void IntLigne::Enqueue(int no)
+{
+ if ( firstAc < 0 ) {
+ firstAc = lastAc = no;
+ bords[no].prev = bords[no].next = -1;
+ } else {
+ bords[no].next = -1;
+ bords[no].prev = lastAc;
+ bords[lastAc].next = no;
+ lastAc = no;
+ }
+}
+
+
+void IntLigne::Dequeue(int no)
+{
+ if ( no == firstAc ) {
+ if ( no == lastAc ) {
+ firstAc = lastAc = -1;
+ } else {
+ firstAc = bords[no].next;
+ }
+ } else if ( no == lastAc ) {
+ lastAc = bords[no].prev;
+ } else {
+ }
+ if ( bords[no].prev >= 0 ) {
+ bords[bords[no].prev].next = bords[no].next;
+ }
+ if ( bords[no].next >= 0 ) {
+ bords[bords[no].next].prev = bords[no].prev;
+ }
+
+ bords[no].prev = bords[no].next = -1;
+}
+
+/**
+ * Rasterization.
+ *
+ * The parameters have the same meaning as in the AlphaLigne class.
+ */
+void IntLigne::Raster(raster_info &dest, void *color, RasterInRunFunc worker)
+{
+ if ( nbRun <= 0 ) {
+ return;
+ }
+
+ int min = runs[0].st;
+ int max = runs[nbRun-1].en;
+ if ( dest.endPix <= min || dest.startPix >= max ) {
+ return;
+ }
+
+ int curRun = -1;
+ for (curRun = 0; curRun < nbRun; curRun++) {
+ if ( runs[curRun].en > dest.startPix ) {
+ break;
+ }
+ }
+
+ if ( curRun >= nbRun ) {
+ return;
+ }
+
+ if ( runs[curRun].st < dest.startPix ) {
+ int nst = runs[curRun].st;
+ int nen = runs[curRun].en;
+ float vst = runs[curRun].vst;
+ float ven = runs[curRun].ven;
+ float nvst = (vst * (nen - dest.startPix) + ven * (dest.startPix - nst)) / ((float) (nen - nst));
+ if ( runs[curRun].en <= dest.endPix ) {
+ (worker)(dest, color, dest.startPix, nvst, runs[curRun].en, runs[curRun].ven);
+ } else {
+ float nven = (vst * (nen - dest.endPix) + ven * (dest.endPix - nst)) / ((float)(nen - nst));
+ (worker)(dest, color, dest.startPix, nvst, dest.endPix, nven);
+ return;
+ }
+ curRun++;
+ }
+
+ for (; (curRun < nbRun && runs[curRun].en <= dest.endPix); curRun++) {
+ (worker)(dest, color, runs[curRun].st, runs[curRun].vst, runs[curRun].en, runs[curRun].ven);
+//Buffer::RasterRun(*dest,color,runs[curRun].st,runs[curRun].vst,runs[curRun].en,runs[curRun].ven);
+ }
+
+ if ( curRun >= nbRun ) {
+ return;
+ }
+
+ if ( runs[curRun].st < dest.endPix && runs[curRun].en > dest.endPix ) {
+ int const nst = runs[curRun].st;
+ int const nen = runs[curRun].en;
+ float const vst = runs[curRun].vst;
+ float const ven = runs[curRun].ven;
+ float const nven = (vst * (nen - dest.endPix) + ven * (dest.endPix - nst)) / ((float)(nen - nst));
+
+ (worker)(dest,color,runs[curRun].st,runs[curRun].vst,dest.endPix,nven);
+ curRun++;
+ }
+}
+
+
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/livarot/int-line.h b/src/livarot/int-line.h
new file mode 100644
index 0000000..576cfcf
--- /dev/null
+++ b/src/livarot/int-line.h
@@ -0,0 +1,119 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * TODO: insert short description here
+ *//*
+ * Authors: see git history
+ *
+ * Copyright (C) 2018 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+#ifndef INKSCAPE_LIVAROT_INT_LINE_H
+#define INKSCAPE_LIVAROT_INT_LINE_H
+
+#include "livarot/LivarotDefs.h"
+#include "object/object-set.h" // For BooleanOp
+
+/** \file
+ * Coverage with integer boundaries.
+ */
+
+class BitLigne;
+class FloatLigne;
+
+/// A run with integer boundaries.
+struct int_ligne_run {
+ int st;
+ int en;
+ float vst;
+ float ven;
+};
+
+/// Integer boundary.
+struct int_ligne_bord {
+ int pos;
+ bool start;
+ float val;
+ int other;
+ int prev;
+ int next;
+};
+
+/**
+ * Coverage with integer boundaries.
+ *
+ * This is what we want for actual rasterization. It contains the same
+ * stuff as FloatLigne, but technically only the Copy() functions are used.
+ */
+class IntLigne {
+public:
+
+ int nbBord;
+ int maxBord;
+ int_ligne_bord* bords;
+
+ int nbRun;
+ int maxRun;
+ int_ligne_run* runs;
+
+ int firstAc;
+ int lastAc;
+
+ IntLigne();
+ virtual ~IntLigne();
+
+ void Reset();
+ int AddBord(int spos, float sval, int epos, float eval);
+
+ void Flatten();
+
+ void Affiche();
+
+ int AddRun(int st, int en, float vst, float ven);
+
+ void Booleen(IntLigne* a, IntLigne* b, BooleanOp mod);
+
+ void Copy(IntLigne* a);
+ void Copy(FloatLigne* a);
+ void Copy(BitLigne* a);
+ void Copy(int nbSub,BitLigne **a);
+
+ void Enqueue(int no);
+ void Dequeue(int no);
+ float RemainingValAt(int at);
+
+ static int CmpBord(void const *p1, void const *p2) {
+ int_ligne_bord const *d1 = reinterpret_cast<int_ligne_bord const *>(p1);
+ int_ligne_bord const *d2 = reinterpret_cast<int_ligne_bord const *>(p2);
+
+ if ( d1->pos == d2->pos ) {
+ if ( d1->start && !(d2->start) ) {
+ return 1;
+ }
+ if ( !(d1->start) && d2->start ) {
+ return -1;
+ }
+ return 0;
+ }
+ return (( d1->pos < d2->pos ) ? -1 : 1);
+ };
+
+ inline float ValAt(int at, int ps, int pe, float vs, float ve) {
+ return ((at - ps) * ve + (pe - at) * vs) / (pe - ps);
+ };
+
+ void Raster(raster_info &dest, void *color, RasterInRunFunc worker);
+};
+
+
+#endif
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/livarot/path-description.cpp b/src/livarot/path-description.cpp
new file mode 100644
index 0000000..0da2d0a
--- /dev/null
+++ b/src/livarot/path-description.cpp
@@ -0,0 +1,146 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * TODO: insert short description here
+ *//*
+ * Authors: see git history
+ *
+ * Copyright (C) 2011 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+#include "livarot/path-description.h"
+#include <2geom/affine.h>
+
+PathDescr *PathDescrMoveTo::clone() const
+{
+ return new PathDescrMoveTo(*this);
+}
+
+void PathDescrMoveTo::dumpSVG(Inkscape::SVGOStringStream& s, Geom::Point const &/*last*/) const
+{
+ s << "M " << p[Geom::X] << " " << p[Geom::Y] << " ";
+}
+
+void PathDescrMoveTo::dump(std::ostream &s) const
+{
+ /* localizing ok */
+ s << " m " << p[Geom::X] << " " << p[Geom::Y];
+}
+
+void PathDescrLineTo::dumpSVG(Inkscape::SVGOStringStream& s, Geom::Point const &/*last*/) const
+{
+ s << "L " << p[Geom::X] << " " << p[Geom::Y] << " ";
+}
+
+PathDescr *PathDescrLineTo::clone() const
+{
+ return new PathDescrLineTo(*this);
+}
+
+void PathDescrLineTo::dump(std::ostream &s) const
+{
+ /* localizing ok */
+ s << " l " << p[Geom::X] << " " << p[Geom::Y];
+}
+
+PathDescr *PathDescrBezierTo::clone() const
+{
+ return new PathDescrBezierTo(*this);
+}
+
+void PathDescrBezierTo::dump(std::ostream &s) const
+{
+ /* localizing ok */
+ s << " b " << p[Geom::X] << " " << p[Geom::Y] << " " << nb;
+}
+
+PathDescr *PathDescrIntermBezierTo::clone() const
+{
+ return new PathDescrIntermBezierTo(*this);
+}
+
+void PathDescrIntermBezierTo::dump(std::ostream &s) const
+{
+ /* localizing ok */
+ s << " i " << p[Geom::X] << " " << p[Geom::Y];
+}
+
+void PathDescrCubicTo::dumpSVG(Inkscape::SVGOStringStream& s, Geom::Point const &last) const
+{
+ // To see why these weird factors of 3 exist here, refer to the documentation in the header file.
+ s << "C "
+ << last[Geom::X] + start[0] / 3 << " "
+ << last[Geom::Y] + start[1] / 3 << " "
+ << p[Geom::X] - end[0] / 3 << " "
+ << p[Geom::Y] - end[1] / 3 << " "
+ << p[Geom::X] << " "
+ << p[Geom::Y] << " ";
+}
+
+PathDescr *PathDescrCubicTo::clone() const
+{
+ return new PathDescrCubicTo(*this);
+}
+
+void PathDescrCubicTo::dump(std::ostream &s) const
+{
+ /* localizing ok */
+ s << " c "
+ << p[Geom::X] << " " << p[Geom::Y] << " "
+ << start[Geom::X] << " " << start[Geom::Y] << " "
+ << end[Geom::X] << " " << end[Geom::Y] << " ";
+}
+
+void PathDescrArcTo::dumpSVG(Inkscape::SVGOStringStream& s, Geom::Point const &/*last*/) const
+{
+ s << "A "
+ << rx << " "
+ << ry << " "
+ << angle << " "
+ << (large ? "1" : "0") << " "
+ << (clockwise ? "0" : "1") << " "
+ << p[Geom::X] << " "
+ << p[Geom::Y] << " ";
+}
+
+PathDescr *PathDescrArcTo::clone() const
+{
+ return new PathDescrArcTo(*this);
+}
+
+void PathDescrArcTo::dump(std::ostream &s) const
+{
+ /* localizing ok */
+ s << " a "
+ << p[Geom::X] << " " << p[Geom::Y] << " "
+ << rx << " " << ry << " "
+ << angle << " "
+ << (clockwise ? 1 : 0) << " "
+ << (large ? 1 : 0);
+}
+
+PathDescr *PathDescrForced::clone() const
+{
+ return new PathDescrForced(*this);
+}
+
+void PathDescrClose::dumpSVG(Inkscape::SVGOStringStream& s, Geom::Point const &/*last*/) const
+{
+ s << "z ";
+}
+
+PathDescr *PathDescrClose::clone() const
+{
+ return new PathDescrClose(*this);
+}
+
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/livarot/path-description.h b/src/livarot/path-description.h
new file mode 100644
index 0000000..57c9eb1
--- /dev/null
+++ b/src/livarot/path-description.h
@@ -0,0 +1,262 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * TODO: insert short description here
+ *//*
+ * Authors: see git history
+ *
+ * Copyright (C) 2018 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+#ifndef SEEN_INKSCAPE_LIVAROT_PATH_DESCRIPTION_H
+#define SEEN_INKSCAPE_LIVAROT_PATH_DESCRIPTION_H
+
+#include <2geom/point.h>
+#include "svg/stringstream.h"
+
+// path description commands
+/* FIXME: these should be unnecessary once the refactoring of the path
+** description stuff is finished.
+*/
+
+/**
+ * An enum to store the path command type. A number is assigned to each type and the
+ * PathDescr class stores a flag variable that stores the number to know which type it is.
+ */
+enum
+{
+ descr_moveto = 0, /*!< A MoveTo path command */
+ descr_lineto = 1, /*!< A LineTo path command */
+ descr_cubicto = 2, /*!< A CubicTo path command. Basically a cubic Bezier */
+ descr_bezierto = 3, /*!< A BezierTo path command is a quadratic Bezier spline. It can contain as many control points as you want to
+ add. The BezierTo instruction only stores the final point and the total number of control points. The actual
+ control points are stored in descr_interm_bezier instructions. One for each control point. */
+ descr_arcto = 4, /*!< An elliptical arc */
+ descr_close = 5, /*!< A close path command */
+ descr_interm_bezier = 6, /*!< Control point for the last BezierTo instruction */
+ descr_forced = 7, /*!< Not exactly sure what a forced point means. As far as I have seen in the simplify code, a forced
+ point is preferred to be kept. The simplification algorithm would make sure the forced point makes
+ its way to the final result. However, as far as I can see, forced point stuff is not used in Inkscape.
+ TODO: Explore how forced points might be being used when Simplify is done after Boolean Ops */
+ descr_type_mask = 15 /*!< As mentioned above, the flag variable of PathDescr stores the type of the command using this enum. The higher bits
+ (after the first four (bit 0 to bit 3) could be used for other flag stuff, so this descr_type_mask can be used to just
+ extract the type. 15 in HEX is 0xF and in BIN is 0000 1111 so ANDing with it will zero any higher bits just leaving you
+ with the type. */
+};
+
+/**
+ * A base class for Livarot's path commands. Each curve type such as Line, CubicBezier
+ * derives from this base class.
+ */
+struct PathDescr
+{
+ PathDescr() : flags(0), associated(-1), tSt(0), tEn(1) {}
+ PathDescr(int f) : flags(f), associated(-1), tSt(0), tEn(1) {}
+ virtual ~PathDescr() = default;
+
+ int getType() const { return flags & descr_type_mask; }
+ void setType(int t) {
+ flags &= ~descr_type_mask;
+ flags |= t;
+ }
+
+ /**
+ * A virtual function that derived classes will implement. Dumps the SVG path d attribute
+ * for this path description.
+ *
+ * @param s The stream to put the SVG description in.
+ * @param last The last point before this path description. This is needed for the computation
+ * of SVG descriptions of instructions such as Cubic and Arc.
+ */
+ virtual void dumpSVG(Inkscape::SVGOStringStream &/*s*/, Geom::Point const &/*last*/) const {}
+
+ /**
+ * A virtual function that derived classes will implement. Returns a newly allocated copy
+ * of the path description.
+ */
+ virtual PathDescr *clone() const = 0;
+
+ /**
+ * A virtual function that derived classes will implement. Similar to dumpSVG however this
+ * prints a simpler path description that's not SVG, only used for debugging purposes. Maybe
+ * the motivation was to support instructions such as BezierTo and IntermBezierTo which do
+ * not have SVG path description equivalents.
+ *
+ * @param s The stream to print to.
+ */
+ virtual void dump(std::ostream &/*s*/) const {}
+
+ int flags; /*!< Lower 4 bits contain the type of the path description as decided by the enum
+ above, upper bits could contain other information but don't know if they really do at all */
+ int associated; /*!< Index of the last polyline point associated with this path description. Interestingly, Path::ConvertWithBackData
+ doesn't set this field at all while Path::Convert and Path::ConvertEvenLines do. */
+ double tSt; /*!< By default set to 0. No idea if this is used at all. TODO */
+ double tEn; /*!< By default set to 1. No idea if this is used at all. TODO */
+};
+
+/**
+ * A MoveTo path command.
+ */
+struct PathDescrMoveTo : public PathDescr
+{
+ PathDescrMoveTo(Geom::Point const &pp)
+ : PathDescr(descr_moveto), p(pp) {}
+
+ void dumpSVG(Inkscape::SVGOStringStream &s, Geom::Point const &last) const override;
+ PathDescr *clone() const override;
+ void dump(std::ostream &s) const override;
+
+ Geom::Point p; /*!< The point to move to. */
+};
+
+/**
+ * A LineTo path command.
+ */
+struct PathDescrLineTo : public PathDescr
+{
+ PathDescrLineTo(Geom::Point const &pp)
+ : PathDescr(descr_lineto), p(pp) {}
+
+ void dumpSVG(Inkscape::SVGOStringStream &s, Geom::Point const &last) const override;
+ PathDescr *clone() const override;
+ void dump(std::ostream &s) const override;
+
+ Geom::Point p; /*!< The point to draw a line to. */
+};
+
+// quadratic bezier curves: a set of control points, and an endpoint
+
+/**
+ * A quadratic bezier spline
+ *
+ * Stores the final point as well as the total number of control points. The control
+ * points will exist in the path commands following this one which would be of the type
+ * PathDescrIntermBezierTo.
+ */
+struct PathDescrBezierTo : public PathDescr
+{
+ PathDescrBezierTo(Geom::Point const &pp, int n)
+ : PathDescr(descr_bezierto), p(pp), nb(n) {}
+
+ PathDescr *clone() const override;
+ void dump(std::ostream &s) const override;
+
+ Geom::Point p; /*!< The final point of the quadratic Bezier spline. */
+ int nb; /*!< The total number of control points. The path commands following this one of the type PathDescrIntermBezierTo
+ will store these control points, one in each one. */
+};
+
+/* FIXME: I don't think this should be necessary */
+
+/**
+ * Intermediate quadratic Bezier spline command.
+ *
+ * These store the control points needed by the PathDescrBezierTo instruction.
+ */
+struct PathDescrIntermBezierTo : public PathDescr
+{
+ PathDescrIntermBezierTo()
+ : PathDescr(descr_interm_bezier) , p(0, 0) {}
+ PathDescrIntermBezierTo(Geom::Point const &pp)
+ : PathDescr(descr_interm_bezier), p(pp) {}
+
+ PathDescr *clone() const override;
+ void dump(std::ostream &s) const override;
+
+ Geom::Point p; /*!< The control point. */
+};
+
+/**
+ * Cubic Bezier path command.
+ *
+ * There is something funny about this one. A typical BezierCurve consists of points
+ * p0, p1, p2, p3 where p1 and p2 are control points. This is a command so it's
+ * quite expected that p0 is not needed. What's interesting is that instead of storing
+ * p1 and p2, (p1 - p0) * 3 and (p3 - p2) * 3 are stored in start and end respectively.
+ * I can't see a good reason for why this was done. Because of this, there is additional
+ * mess required in the formulas for bezier curve splitting.
+ */
+struct PathDescrCubicTo : public PathDescr
+{
+ PathDescrCubicTo(Geom::Point const &pp, Geom::Point const &s, Geom::Point const& e)
+ : PathDescr(descr_cubicto), p(pp), start(s), end(e) {}
+
+ void dumpSVG(Inkscape::SVGOStringStream &s, Geom::Point const &last) const override;
+ PathDescr *clone() const override;
+ void dump(std::ostream &s) const override;
+
+ Geom::Point p; /*!< The final point of the bezier curve. */
+ Geom::Point start; /*!< 3 * (p1 - p0) where p0 is the start point of the cubic bezier and
+ p1 is the first control point. */
+ Geom::Point end; /*!< 3 * (p3 - p2) where p3 is the final point of the cubic bezier and
+ p2 is the second control point. */
+};
+
+/**
+ * Elliptical Arc path command.
+ *
+ * Exactly the equivalent of an SVG elliptical arc description.
+ */
+struct PathDescrArcTo : public PathDescr
+{
+ PathDescrArcTo(Geom::Point const &pp, double x, double y, double a, bool l, bool c)
+ : PathDescr(descr_arcto), p(pp), rx(x), ry(y), angle(a), large(l), clockwise(c) {}
+
+ void dumpSVG(Inkscape::SVGOStringStream &s, Geom::Point const &last) const override;
+ PathDescr *clone() const override;
+ void dump(std::ostream &s) const override;
+
+ Geom::Point p; /*!< The final point of the arc. */
+ double rx; /*!< The radius in the x direction. */
+ double ry; /*!< The radius in the y direction. */
+ double angle; /*!< The angle it makes with the x axis in degrees. TODO confirm that */
+ bool large; /*!< The large arc or the small one? */
+ bool clockwise; /*!< Clockwise arc or anti-clockwise one? */
+};
+
+/**
+ * A forced point path command.
+ *
+ * TODO: This needs more research. Why is this useful and where?
+ */
+struct PathDescrForced : public PathDescr
+{
+ PathDescrForced() : PathDescr(descr_forced), p(0, 0) {}
+
+ PathDescr *clone() const override;
+
+ /* FIXME: not sure whether _forced should have a point associated with it;
+ ** Path::ConvertForcedToMoveTo suggests that maybe it should.
+ */
+ Geom::Point p; /*!< The forced point itself? */
+};
+
+
+/**
+ * Close Path instruction.
+ */
+struct PathDescrClose : public PathDescr
+{
+ PathDescrClose() : PathDescr(descr_close) {}
+
+ void dumpSVG(Inkscape::SVGOStringStream &s, Geom::Point const &last) const override;
+ PathDescr *clone() const override;
+
+ /* FIXME: not sure whether _forced should have a point associated with it;
+ ** Path::ConvertForcedToMoveTo suggests that maybe it should.
+ */
+ Geom::Point p; /*!< Useless since close instruction needs no point. */
+};
+
+#endif
+
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/livarot/sweep-event-queue.h b/src/livarot/sweep-event-queue.h
new file mode 100644
index 0000000..f0fb479
--- /dev/null
+++ b/src/livarot/sweep-event-queue.h
@@ -0,0 +1,108 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * A container of intersection events.
+ *//*
+ * Authors: see git history
+ *
+ * Copyright (C) 2010 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+#ifndef SEEN_LIVAROT_SWEEP_EVENT_QUEUE_H
+#define SEEN_LIVAROT_SWEEP_EVENT_QUEUE_H
+
+#include <2geom/forward.h>
+class SweepEvent;
+class SweepTree;
+
+
+/**
+ * The structure to hold the intersections events encountered during the sweep. It's an array of
+ * SweepEvent (not allocated with "new SweepEvent[n]" but with a malloc). There's a list of
+ * indices because it's a binary heap: inds[i] tell that events[inds[i]] has position i in the
+ * heap. Each SweepEvent has a field to store its index in the heap, too.
+ */
+class SweepEventQueue
+{
+public:
+ SweepEventQueue(int s);
+ virtual ~SweepEventQueue();
+
+ /**
+ * Number of events currently stored.
+ *
+ * @return The number of elements stored.
+ */
+ int size() const { return nbEvt; }
+
+ /** Look for the top most intersection in the heap
+ *
+ * @param iLeft Reference that function will set to the left node of top most intersection.
+ * @param iRight Reference that function will set to the right node of top most intersection.
+ * @param oPt Reference that function will set to the intersection point of top most intersection.
+ * @param itl Reference that function will set to time of top most intersection on the left edge.
+ * @param itr Reference that function will set to time of top most intersection on the right edge.
+ *
+ * @return True if an intersection event exists false otherwise.
+ */
+ bool peek(SweepTree * &iLeft, SweepTree * &iRight, Geom::Point &oPt, double &itl, double &itr);
+
+ /** Extract the top most intersection from the heap
+ *
+ * @param iLeft Reference that function will set to the left node of top most intersection.
+ * @param iRight Reference that function will set to the right node of top most intersection.
+ * @param oPt Reference that function will set to the point of top most intersection.
+ * @param itl Reference that function will set to time of top most intersection on the left edge.
+ * @param itr Reference that function will set to time of top most intersection on the right edge.
+ *
+ * @return True if an intersection event exists false otherwise.
+ */
+ bool extract(SweepTree * &iLeft, SweepTree * &iRight, Geom::Point &oPt, double &itl, double &itr);
+
+ /** Add an intersection in the binary heap
+ *
+ * @param iLeft Pointer to left node of intersection.
+ * @param iRight Pointer to right node of intersection.
+ * @param iPt Point of intersection.
+ * @param itl Time of intersection on the left edge.
+ * @param itr Time of intersection on the right edge.
+ */
+ SweepEvent *add(SweepTree *iLeft, SweepTree *iRight, Geom::Point &iPt, double itl, double itr);
+
+ /**
+ * Remove event from the event queue. Make sure to clear the evt pointers from the nodes
+ * involved.
+ *
+ * @param e The event to remove.
+ */
+ void remove(SweepEvent *e);
+
+ /**
+ * Relocate the event `e` to the location to.
+ *
+ * This will place all data of `e` in `to` and also update any
+ * evt pointers held by the intersection nodes.
+ *
+ * @param e The SweepEvent to relocate.
+ * @param to The index of the location where we want to relocate `e` to.
+ */
+ void relocate(SweepEvent *e, int to);
+
+private:
+ int nbEvt; /*!< Number of events currently in the heap. */
+ int maxEvt; /*!< Allocated size of the heap. */
+ int *inds; /*!< Indices. */
+ SweepEvent *events; /*!< Sweep events. */
+};
+
+#endif /* !SEEN_LIVAROT_SWEEP_EVENT_QUEUE_H */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/livarot/sweep-event.cpp b/src/livarot/sweep-event.cpp
new file mode 100644
index 0000000..bb9e4e7
--- /dev/null
+++ b/src/livarot/sweep-event.cpp
@@ -0,0 +1,284 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * TODO: insert short description here
+ *//*
+ * Authors: see git history
+ *
+ * Copyright (C) 2018 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+#include <glib.h>
+#include "livarot/sweep-event-queue.h"
+#include "livarot/sweep-tree.h"
+#include "livarot/sweep-event.h"
+#include "livarot/Shape.h"
+
+SweepEventQueue::SweepEventQueue(int s) : nbEvt(0), maxEvt(s)
+{
+ /* FIXME: use new[] for this, but this causes problems when delete[]
+ ** calls the SweepEvent destructors.
+ */
+ events = (SweepEvent *) g_malloc(maxEvt * sizeof(SweepEvent));
+ inds = new int[maxEvt];
+}
+
+SweepEventQueue::~SweepEventQueue()
+{
+ g_free(events);
+ delete []inds;
+}
+
+SweepEvent *SweepEventQueue::add(SweepTree *iLeft, SweepTree *iRight, Geom::Point &px, double itl, double itr)
+{
+ if (nbEvt > maxEvt) {
+ return nullptr;
+ }
+
+ int const n = nbEvt++;
+ events[n].MakeNew (iLeft, iRight, px, itl, itr);
+
+ SweepTree *t[2] = { iLeft, iRight };
+ for (auto & i : t) {
+ Shape *s = i->src;
+ Shape::dg_arete const &e = s->getEdge(i->bord);
+ int const n = std::max(e.st, e.en);
+ s->pData[n].pending++;;
+ }
+
+ events[n].ind = n;
+ inds[n] = n;
+
+ int curInd = n;
+ while (curInd > 0) {
+ int const half = (curInd - 1) / 2;
+ int const no = inds[half];
+ if (px[1] < events[no].posx[1]
+ || (px[1] == events[no].posx[1] && px[0] < events[no].posx[0]))
+ {
+ events[n].ind = half;
+ events[no].ind = curInd;
+ inds[half] = n;
+ inds[curInd] = no;
+ } else {
+ break;
+ }
+
+ curInd = half;
+ }
+
+ return events + n;
+}
+
+
+
+bool SweepEventQueue::peek(SweepTree * &iLeft, SweepTree * &iRight, Geom::Point &px, double &itl, double &itr)
+{
+ if (nbEvt <= 0) {
+ return false;
+ }
+
+ SweepEvent const &e = events[inds[0]];
+
+ iLeft = e.sweep[LEFT];
+ iRight = e.sweep[RIGHT];
+ px = e.posx;
+ itl = e.tl;
+ itr = e.tr;
+
+ return true;
+}
+
+bool SweepEventQueue::extract(SweepTree * &iLeft, SweepTree * &iRight, Geom::Point &px, double &itl, double &itr)
+{
+ if (nbEvt <= 0) {
+ return false;
+ }
+
+ SweepEvent &e = events[inds[0]];
+
+ iLeft = e.sweep[LEFT];
+ iRight = e.sweep[RIGHT];
+ px = e.posx;
+ itl = e.tl;
+ itr = e.tr;
+ remove(&e);
+
+ return true;
+}
+
+
+void SweepEventQueue::remove(SweepEvent *e)
+{
+ if (nbEvt <= 1) {
+ e->MakeDelete ();
+ nbEvt = 0;
+ return;
+ }
+
+ int const n = e->ind;
+ int to = inds[n];
+ e->MakeDelete();
+ relocate(&events[--nbEvt], to);
+
+ int const moveInd = nbEvt;
+ if (moveInd == n) {
+ return;
+ }
+
+ to = inds[moveInd];
+
+ events[to].ind = n;
+ inds[n] = to;
+
+ int curInd = n;
+ Geom::Point const px = events[to].posx;
+ bool didClimb = false;
+ while (curInd > 0) {
+ int const half = (curInd - 1) / 2;
+ int const no = inds[half];
+ if (px[1] < events[no].posx[1]
+ || (px[1] == events[no].posx[1] && px[0] < events[no].posx[0]))
+ {
+ events[to].ind = half;
+ events[no].ind = curInd;
+ inds[half] = to;
+ inds[curInd] = no;
+ didClimb = true;
+ } else {
+ break;
+ }
+ curInd = half;
+ }
+
+ if (didClimb) {
+ return;
+ }
+
+ while (2 * curInd + 1 < nbEvt) {
+ int const child1 = 2 * curInd + 1;
+ int const child2 = child1 + 1;
+ int const no1 = inds[child1];
+ int const no2 = inds[child2];
+ if (child2 < nbEvt) {
+ if (px[1] > events[no1].posx[1]
+ || (px[1] == events[no1].posx[1]
+ && px[0] > events[no1].posx[0]))
+ {
+ if (events[no2].posx[1] > events[no1].posx[1]
+ || (events[no2].posx[1] == events[no1].posx[1]
+ && events[no2].posx[0] > events[no1].posx[0]))
+ {
+ events[to].ind = child1;
+ events[no1].ind = curInd;
+ inds[child1] = to;
+ inds[curInd] = no1;
+ curInd = child1;
+ } else {
+ events[to].ind = child2;
+ events[no2].ind = curInd;
+ inds[child2] = to;
+ inds[curInd] = no2;
+ curInd = child2;
+ }
+ } else {
+ if (px[1] > events[no2].posx[1]
+ || (px[1] == events[no2].posx[1]
+ && px[0] > events[no2].posx[0]))
+ {
+ events[to].ind = child2;
+ events[no2].ind = curInd;
+ inds[child2] = to;
+ inds[curInd] = no2;
+ curInd = child2;
+ } else {
+ break;
+ }
+ }
+ } else {
+ if (px[1] > events[no1].posx[1]
+ || (px[1] == events[no1].posx[1]
+ && px[0] > events[no1].posx[0]))
+ {
+ events[to].ind = child1;
+ events[no1].ind = curInd;
+ inds[child1] = to;
+ inds[curInd] = no1;
+ }
+
+ break;
+ }
+ }
+}
+
+
+
+
+void SweepEventQueue::relocate(SweepEvent *e, int to)
+{
+ if (inds[e->ind] == to) {
+ return; // j'y suis deja
+ }
+
+ events[to] = *e;
+
+ e->sweep[LEFT]->evt[RIGHT] = events + to;
+ e->sweep[RIGHT]->evt[LEFT] = events + to;
+ inds[e->ind] = to;
+}
+
+
+/*
+ * a simple binary heap
+ * it only contains intersection events
+ * the regular benley-ottman stuffs the segment ends in it too, but that not needed here since theses points
+ * are already sorted. and the binary heap is much faster with only intersections...
+ * the code sample on which this code is based comes from purists.org
+ */
+SweepEvent::SweepEvent()
+{
+ MakeNew (nullptr, nullptr, Geom::Point(0, 0), 0, 0);
+}
+
+SweepEvent::~SweepEvent()
+{
+ MakeDelete();
+}
+
+void SweepEvent::MakeNew(SweepTree *iLeft, SweepTree *iRight, Geom::Point const &px, double itl, double itr)
+{
+ ind = -1;
+ posx = px;
+ tl = itl;
+ tr = itr;
+ sweep[LEFT] = iLeft;
+ sweep[RIGHT] = iRight;
+ sweep[LEFT]->evt[RIGHT] = this;
+ sweep[RIGHT]->evt[LEFT] = this;
+}
+
+void SweepEvent::MakeDelete()
+{
+ for (int i = 0; i < 2; i++) {
+ if (sweep[i]) {
+ Shape *s = sweep[i]->src;
+ Shape::dg_arete const &e = s->getEdge(sweep[i]->bord);
+ int const n = std::max(e.st, e.en);
+ s->pData[n].pending--;
+ }
+
+ sweep[i]->evt[1 - i] = nullptr;
+ sweep[i] = nullptr;
+ }
+}
+
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/livarot/sweep-event.h b/src/livarot/sweep-event.h
new file mode 100644
index 0000000..2776c7a
--- /dev/null
+++ b/src/livarot/sweep-event.h
@@ -0,0 +1,72 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * TODO: insert short description here
+ *//*
+ * Authors: see git history
+ *
+ * Copyright (C) 2018 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+#ifndef INKSCAPE_LIVAROT_SWEEP_EVENT_H
+#define INKSCAPE_LIVAROT_SWEEP_EVENT_H
+/** \file
+ * Intersection events.
+ */
+
+#include <2geom/point.h>
+class SweepTree;
+
+
+/**
+ * An intersection event structure to record any intersections that are
+ * detected (predicted) during the sweepline.
+ */
+class SweepEvent
+{
+public:
+ SweepTree *sweep[2]; /*!< Nodes associated with the left and right edge of the intersection. */
+
+ Geom::Point posx; /*!< Point of the intersection. */
+ double tl; /*!< Time value of the intersection on the left edge (tl).*/
+ double tr; /*!< Time value of the intersection on the right edge (tr). */
+
+ int ind; /*!< Index in the binary heap. */
+
+ SweepEvent(); // not used.
+ virtual ~SweepEvent(); // not used.
+
+ /**
+ * Initialize the sweep event.
+ *
+ * @param iLeft The left node of the intersection.
+ * @param iRight The right node of the intersection.
+ * @param iPt The intersection point.
+ * @param itl The time value of the intersection on the left edge.
+ * @param itr The time value of the intersection on the right edge.
+ */
+ void MakeNew (SweepTree * iLeft, SweepTree * iRight, Geom::Point const &iPt,
+ double itl, double itr);
+
+ /// Void a SweepEvent structure.
+
+ /**
+ * Empty the sweep event data.
+ *
+ * Also reset event pointers of any SweepTree nodes that might point to this event.
+ */
+ void MakeDelete ();
+};
+
+
+#endif /* !INKSCAPE_LIVAROT_SWEEP_EVENT_H */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/livarot/sweep-tree-list.cpp b/src/livarot/sweep-tree-list.cpp
new file mode 100644
index 0000000..97640fd
--- /dev/null
+++ b/src/livarot/sweep-tree-list.cpp
@@ -0,0 +1,56 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * TODO: insert short description here
+ *//*
+ * Authors: see git history
+ *
+ * Copyright (C) 2018 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+#include <glib.h>
+#include "livarot/sweep-tree.h"
+#include "livarot/sweep-tree-list.h"
+
+
+SweepTreeList::SweepTreeList(int s) :
+ nbTree(0),
+ maxTree(s),
+ trees((SweepTree *) g_malloc(s * sizeof(SweepTree))),
+ racine(nullptr)
+{
+ /* FIXME: Use new[] for trees initializer above, but watch out for bad things happening when
+ * SweepTree::~SweepTree is called.
+ */
+}
+
+
+SweepTreeList::~SweepTreeList()
+{
+ g_free(trees);
+ trees = nullptr;
+}
+
+
+SweepTree *SweepTreeList::add(Shape *iSrc, int iBord, int iWeight, int iStartPoint, Shape */*iDst*/)
+{
+ if (nbTree >= maxTree) {
+ return nullptr;
+ }
+
+ int const n = nbTree++;
+ trees[n].MakeNew(iSrc, iBord, iWeight, iStartPoint);
+
+ return trees + n;
+}
+
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/livarot/sweep-tree-list.h b/src/livarot/sweep-tree-list.h
new file mode 100644
index 0000000..25c350c
--- /dev/null
+++ b/src/livarot/sweep-tree-list.h
@@ -0,0 +1,84 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * TODO: insert short description here
+ *//*
+ * Authors: see git history
+ *
+ * Copyright (C) 2010 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+/** @file
+ * @brief SweepTreeList definition
+ */
+
+#ifndef INKSCAPE_LIVAROT_SWEEP_TREE_LIST_H
+#define INKSCAPE_LIVAROT_SWEEP_TREE_LIST_H
+
+class Shape;
+class SweepTree;
+
+/**
+ * The sweepline tree to store a linear sequence of edges that intersect with the sweepline
+ * in the exact order.
+ *
+ * This could just be a double-linked list but it also an AVL search tree
+ * to quickly find edges.
+ *
+ * In this documentation, a SweepTree instance is referred to as a node.
+ *
+ * This is a class to store the nodes. Most interesting stuff happens in the class
+ * SweepTree or its parent class AVLTree.h This just keeps the list of nodes and the pointer
+ * to the root node.
+ */
+class SweepTreeList {
+public:
+ int nbTree; /*!< Number of nodes in the tree. */
+ int const maxTree; /*!< Max number of nodes in the tree. */
+ SweepTree *trees; /*!< The array of nodes. */
+ SweepTree *racine; /*!< Root of the tree. */
+
+ /**
+ * Constructor to create a new SweepTreeList.
+ *
+ * @param s The number of maximum nodes it should be able to hold.
+ */
+ SweepTreeList(int s);
+
+ /**
+ * The destructor. But didn't have to be virtual.
+ */
+ virtual ~SweepTreeList();
+
+ /**
+ * Create a new node and add it. This doesn't do any insertion in tree though. It just
+ * creates the node and puts it in the list of nodes. The actual insertion would need
+ * to be done by calling SweepTree::Insert or in the speical case SweepTree::InsertAt.
+ *
+ * @param iSrc A pointer to the shape.
+ * @param iBord The edge index.
+ * @param iWeight Weight of the of the edge. Weight of 2 is equivalent of two identical edges
+ * with same direction on top of each other.
+ * @param iStartPoint The point at which this node got added. (the upper endpoint if sweeping
+ * top to bottom).
+ * @param iDst Supposed to be the destination shape. The Shape on which Shape::ConvertToShape
+ * was called. `iSrc` is the parameter that was passed in Shape::ConvertToShape. Useless
+ * parameter though, not used.
+ * @param The address of the newly added node. Can be used later for Inserting it or anything
+ * else.
+ */
+ SweepTree *add(Shape *iSrc, int iBord, int iWeight, int iStartPoint, Shape *iDst);
+};
+
+
+#endif /* !INKSCAPE_LIVAROT_SWEEP_TREE_LIST_H */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/livarot/sweep-tree.cpp b/src/livarot/sweep-tree.cpp
new file mode 100644
index 0000000..4536ae3
--- /dev/null
+++ b/src/livarot/sweep-tree.cpp
@@ -0,0 +1,614 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * TODO: insert short description here
+ *//*
+ * Authors: see git history
+ *
+ * Copyright (C) 2018 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+#include "livarot/sweep-event-queue.h"
+#include "livarot/sweep-tree-list.h"
+#include "livarot/sweep-tree.h"
+#include "livarot/sweep-event.h"
+#include "livarot/Shape.h"
+
+
+/*
+ * the AVL tree holding the edges intersecting the sweepline
+ * that structure is very sensitive to anything
+ * you have edges stored in nodes, the nodes are sorted in increasing x-order of intersection
+ * with the sweepline, you have the 2 potential intersections of the edge in the node with its
+ * neighbours, plus the fact that it's stored in an array that's realloc'd
+ */
+
+SweepTree::SweepTree()
+{
+ src = nullptr;
+ bord = -1;
+ startPoint = -1;
+ evt[LEFT] = evt[RIGHT] = nullptr;
+ sens = true;
+ //invDirLength=1;
+}
+
+SweepTree::~SweepTree()
+{
+ MakeDelete();
+}
+
+void
+SweepTree::MakeNew(Shape *iSrc, int iBord, int iWeight, int iStartPoint)
+{
+ AVLTree::MakeNew();
+ ConvertTo(iSrc, iBord, iWeight, iStartPoint);
+}
+
+void
+SweepTree::ConvertTo(Shape *iSrc, int iBord, int iWeight, int iStartPoint)
+{
+ src = iSrc;
+ bord = iBord;
+ evt[LEFT] = evt[RIGHT] = nullptr;
+ startPoint = iStartPoint;
+ if (src->getEdge(bord).st < src->getEdge(bord).en) {
+ if (iWeight >= 0)
+ sens = true;
+ else
+ sens = false;
+ } else {
+ if (iWeight >= 0)
+ sens = false;
+ else
+ sens = true;
+ }
+ //invDirLength=src->eData[bord].isqlength;
+ //invDirLength=1/sqrt(src->getEdge(bord).dx*src->getEdge(bord).dx+src->getEdge(bord).dy*src->getEdge(bord).dy);
+}
+
+
+void SweepTree::MakeDelete()
+{
+ for (int i = 0; i < 2; i++) {
+ if (evt[i]) {
+ evt[i]->sweep[1 - i] = nullptr;
+ }
+ evt[i] = nullptr;
+ }
+
+ AVLTree::MakeDelete();
+}
+
+
+// find the position at which node "newOne" should be inserted in the subtree rooted here
+// we want to order with respect to the order of intersections with the sweepline, currently
+// lying at y=px[1].
+// px is the upper endpoint of newOne
+int
+SweepTree::Find(Geom::Point const &px, SweepTree *newOne, SweepTree *&insertL,
+ SweepTree *&insertR, bool sweepSens)
+{
+ // get the edge associated with this node: one point+one direction
+ // since we're dealing with line, the direction (bNorm) is taken downwards
+ Geom::Point bOrig, bNorm;
+ bOrig = src->pData[src->getEdge(bord).st].rx;
+ bNorm = src->eData[bord].rdx;
+ if (src->getEdge(bord).st > src->getEdge(bord).en) {
+ bNorm = -bNorm;
+ }
+ // rotate to get the normal to the edge
+ bNorm=bNorm.ccw();
+
+ Geom::Point diff;
+ diff = px - bOrig;
+
+ // compute (px-orig)^dir to know on which side of this edge the point px lies
+ double y = 0;
+ //if ( startPoint == newOne->startPoint ) {
+ // y=0;
+ //} else {
+ y = dot(bNorm, diff);
+ //}
+ //y*=invDirLength;
+ if (fabs(y) < 0.000001) {
+ // that damn point px lies on me, so i need to consider to direction of the edge in
+ // newOne to know if it goes toward my left side or my right side
+ // sweepSens is needed (actually only used by the Scan() functions) because if the sweepline goes upward,
+ // signs change
+ // prendre en compte les directions
+ Geom::Point nNorm;
+ nNorm = newOne->src->eData[newOne->bord].rdx;
+ if (newOne->src->getEdge(newOne->bord).st >
+ newOne->src->getEdge(newOne->bord).en)
+ {
+ nNorm = -nNorm;
+ }
+ nNorm=nNorm.ccw();
+
+ if (sweepSens) {
+ y = cross(bNorm, nNorm);
+ } else {
+ y = cross(nNorm, bNorm);
+ }
+ if (y == 0) {
+ y = dot(bNorm, nNorm);
+ if (y == 0) {
+ insertL = this;
+ insertR = static_cast<SweepTree *>(elem[RIGHT]);
+ return found_exact;
+ }
+ }
+ }
+ if (y < 0) {
+ if (child[LEFT]) {
+ return (static_cast<SweepTree *>(child[LEFT]))->Find(px, newOne,
+ insertL, insertR,
+ sweepSens);
+ } else {
+ insertR = this;
+ insertL = static_cast<SweepTree *>(elem[LEFT]);
+ if (insertL) {
+ return found_between;
+ } else {
+ return found_on_left;
+ }
+ }
+ } else {
+ if (child[RIGHT]) {
+ return (static_cast<SweepTree *>(child[RIGHT]))->Find(px, newOne,
+ insertL, insertR,
+ sweepSens);
+ } else {
+ insertL = this;
+ insertR = static_cast<SweepTree *>(elem[RIGHT]);
+ if (insertR) {
+ return found_between;
+ } else {
+ return found_on_right;
+ }
+ }
+ }
+ return not_found;
+}
+
+// only find a point's position
+int
+SweepTree::Find(Geom::Point const &px, SweepTree * &insertL,
+ SweepTree * &insertR)
+{
+ Geom::Point bOrig, bNorm;
+ // The start point of the original edge vector
+ bOrig = src->pData[src->getEdge(bord).st].rx;
+ // The edge vector
+ bNorm = src->eData[bord].rdx;
+ // Flip the edge vector if it's bottom to top or horizontal and right to left
+ if (src->getEdge(bord).st > src->getEdge(bord).en)
+ {
+ bNorm = -bNorm;
+ }
+ // rotate the edge vector counter clockwise by 90 degrees
+ bNorm=bNorm.ccw();
+
+ // draw a vector from the start point to the actual point
+ Geom::Point diff;
+ diff = px - bOrig;
+
+ double y = 0;
+ y = dot(bNorm, diff); // take the dot product
+ // ANALOGY FROM DIAGRAM (See doc in header file):
+ // this case is the same as if we are at node (15) and want to add (15). Usually, you can't
+ // add same stuff in a binary search tree but here it's fine to do so (I guess)
+ // In that case, we have found an exact match and the point belongs between 15 and the one
+ // on it's right (16).
+ if (y == 0) // point lies on the edge (or at least the line of the edge)
+ {
+ insertL = this;
+ insertR = static_cast<SweepTree *>(elem[RIGHT]);
+ return found_exact;
+ }
+
+ // ANALOGY FROM DIAGRAM (See doc in header file):
+ // This is the same as inserting 3 while standing at 10, or inserting 3 while at 5 or inserting
+ // 13 while at 15 or inserting 1 while at 2 or inserting 11 while at 12.
+ if (y < 0) // lies to the left of the edge
+ {
+ if (child[LEFT]) // is there child on left? This is true at 10, 5, 15 but not at the nodes such as 2, 6, 12, 16
+ {
+ // if there is a child on left, let the child do the searching
+ return (static_cast<SweepTree *>(child[LEFT]))->Find(px, insertL, // if yes, let that child do the finding now
+ insertR);
+ }
+ else // no child on the left? Means either a leaf node or node has no child on left.
+ {
+ // well we are sure that there is no child on the left, which means this new node goes
+ // to the left of this node, but that doesn't really mean there no left element in the
+ // linked list, there sure can be. For example, if you're inserting 11 while standing
+ // at 12, there is no left child, but in the linked list, there is 10 to the left.
+ insertR = this;
+ insertL = static_cast<SweepTree *>(elem[LEFT]);
+ if (insertL)
+ {
+ return found_between;
+ }
+ else // however, if you're at 2 and inserting 1, there is no left child, but there is also nothing on the left in the linked list either
+ {
+ return found_on_left;
+ }
+ }
+ } // lies to the right of the edge
+ // ANALOGY FROM DIAGRAM (See doc in header file):
+ // This is the same as inserting 14 while standing at 10, 7 while standing at 5, 18 while
+ // standing at 15, 7 while standing at 6, you get the point
+ else
+ {
+ if (child[RIGHT]) // is there a child to the right? If you're at 10 or 5 or 15 there is child on right so you let the child decide where
+ { // new node goes but not if you're at leaf nodes such as 2, 6, 12, 16 or any other node that doesn't have a right child
+ return (static_cast<SweepTree *>(child[RIGHT]))->Find(px, insertL, // let that child do the finding now
+ insertR);
+ }
+ else
+ {
+ // okay so no right child, but stil you can have an element to the right in the linked
+ // list. For example you are at 6 and want to insert 7, no child on the right so we are
+ // sure the new node goes to the right of 6 but there is still 10 to the right in the
+ // double-linked list.
+ insertL = this;
+ insertR = static_cast<SweepTree *>(elem[RIGHT]);
+ if (insertR)
+ {
+ return found_between;
+ }
+ else
+ {
+ return found_on_right;
+ }
+ }
+ }
+ return not_found;
+}
+
+void
+SweepTree::RemoveEvents(SweepEventQueue & queue)
+{
+ RemoveEvent(queue, LEFT);
+ RemoveEvent(queue, RIGHT);
+}
+
+void SweepTree::RemoveEvent(SweepEventQueue &queue, Side s)
+{
+ if (evt[s]) {
+ queue.remove(evt[s]);
+ evt[s] = nullptr;
+ }
+}
+
+int
+SweepTree::Remove(SweepTreeList &list, SweepEventQueue &queue,
+ bool rebalance)
+{
+ RemoveEvents(queue);
+ AVLTree *tempR = static_cast<AVLTree *>(list.racine);
+ int err = AVLTree::Remove(tempR, rebalance);
+ list.racine = static_cast<SweepTree *>(tempR);
+ MakeDelete();
+ if (list.nbTree <= 1)
+ {
+ list.nbTree = 0;
+ list.racine = nullptr;
+ }
+ else
+ {
+ if (list.racine == list.trees + (list.nbTree - 1))
+ list.racine = this;
+ list.trees[--list.nbTree].Relocate(this);
+ }
+ return err;
+}
+
+int
+SweepTree::Insert(SweepTreeList &list, SweepEventQueue &queue,
+ Shape *iDst, int iAtPoint, bool rebalance, bool sweepSens)
+{
+ // if the root node doesn't exist, make this one the root node
+ if (list.racine == nullptr)
+ {
+ list.racine = this;
+ return avl_no_err;
+ }
+ SweepTree *insertL = nullptr;
+ SweepTree *insertR = nullptr;
+ // use the Find call to figure out the exact position where this needs to go
+ int insertion =
+ list.racine->Find(iDst->getPoint(iAtPoint).x, this,
+ insertL, insertR, sweepSens);
+
+ // if the insertion type is found_exact or found_between this new node is getting in between
+ // two existing nodes, which demands that any intersection event that was recorded between
+ // the two must be destroyed now *cuz they are no longer together* :-(
+ if (insertion == found_exact) { // not sure if these if statements are really needed.
+ if (insertR) {
+ insertR->RemoveEvent(queue, LEFT);
+ }
+ if (insertL) {
+ insertL->RemoveEvent(queue, RIGHT);
+ }
+
+ } else if (insertion == found_between) {
+ insertR->RemoveEvent(queue, LEFT);
+ insertL->RemoveEvent(queue, RIGHT);
+ }
+ // let the parent class do the adding now
+ AVLTree *tempR = static_cast<AVLTree *>(list.racine);
+ int err =
+ AVLTree::Insert(tempR, insertion, static_cast<AVLTree *>(insertL),
+ static_cast<AVLTree *>(insertR), rebalance);
+ list.racine = static_cast<SweepTree *>(tempR);
+ return err;
+}
+
+// insertAt() is a speedup on the regular sweepline: if the polygon contains a point of high degree, you
+// get a set of edge that are to be added in the same position. thus you insert one edge with a regular insert(),
+// and then insert all the other in a doubly-linked list fashion. this avoids the Find() call, but is O(d^2) worst-case
+// where d is the number of edge to add in this fashion. hopefully d remains small
+
+int
+SweepTree::InsertAt(SweepTreeList &list, SweepEventQueue &queue,
+ Shape */*iDst*/, SweepTree *insNode, int fromPt,
+ bool rebalance, bool sweepSens)
+{
+ // if root node not set, set it
+ if (list.racine == nullptr)
+ {
+ list.racine = this;
+ return avl_no_err;
+ }
+
+ // the common point between edges
+ Geom::Point fromP;
+ fromP = src->pData[fromPt].rx;
+ // get the edge vector
+ Geom::Point nNorm;
+ nNorm = src->getEdge(bord).dx;
+ // make sure the edge vector is top to bottom or if horizontal
+ if (src->getEdge(bord).st > src->getEdge(bord).en)
+ {
+ nNorm = -nNorm;
+ }
+ if (sweepSens == false) // why tho?
+ {
+ nNorm = -nNorm;
+ }
+
+ Geom::Point bNorm; // the existing edge (kinda the reference node u can say) that we wanna add this one near to
+ bNorm = insNode->src->getEdge(insNode->bord).dx;
+ if (insNode->src->getEdge(insNode->bord).st >
+ insNode->src->getEdge(insNode->bord).en)
+ {
+ bNorm = -bNorm;
+ }
+
+ SweepTree *insertL = nullptr;
+ SweepTree *insertR = nullptr;
+ double ang = cross(bNorm, nNorm); // you can use the diagram in the header documentation to make sense of cross product's direction.
+ if (ang == 0) // node on top of this one, so we just add right here
+ {
+ insertL = insNode;
+ insertR = static_cast<SweepTree *>(insNode->elem[RIGHT]);
+ }
+ else if (ang > 0) // edge is to the left
+ {
+ // initialize such that we are adding this edge between insNode (reference) and whatever is
+ // to it's right, this position will change as we go left now
+ insertL = insNode;
+ insertR = static_cast<SweepTree *>(insNode->elem[RIGHT]);
+
+ // start moving to the left
+ while (insertL)
+ {
+ if (insertL->src == src)
+ {
+ if (insertL->src->getEdge(insertL->bord).st != fromPt
+ && insertL->src->getEdge(insertL->bord).en != fromPt)
+ {
+ break; // if the edge on the left has no endpoint that's fromPt, means we have gone too far, so break
+ // we only case about inserting this at the right position relative to the
+ // existing edges that are connected to fromPt
+ }
+ }
+ else
+ { // TODO: has to do with case when an edge can come from another shape
+ int ils = insertL->src->getEdge(insertL->bord).st;
+ int ile = insertL->src->getEdge(insertL->bord).en;
+ if ((insertL->src->pData[ils].rx[0] != fromP[0]
+ || insertL->src->pData[ils].rx[1] != fromP[1])
+ && (insertL->src->pData[ile].rx[0] != fromP[0]
+ || insertL->src->pData[ile].rx[1] != fromP[1]))
+ {
+ break;
+ }
+ }
+ // bNorm is the new edge (the new reference to which we will compare the new edge (to
+ // add))
+ bNorm = insertL->src->getEdge(insertL->bord).dx;
+ if (insertL->src->getEdge(insertL->bord).st >
+ insertL->src->getEdge(insertL->bord).en)
+ {
+ bNorm = -bNorm;
+ }
+ ang = cross(bNorm, nNorm);
+ if (ang <= 0) // the new edge should go to the right of this one, so break as insertL and insertR are perfect as they are
+ {
+ break;
+ }
+ insertR = insertL; // otherwise go left
+ insertL = static_cast<SweepTree *>(insertR->elem[LEFT]);
+ }
+ }
+ else if (ang < 0) // the new edge goes to the right
+ {
+ // initialize such that we are adding this edge between insNode (reference) and whatever is
+ // to it's right, this position will change as we go left now
+ insertL = insNode;
+ insertR = static_cast<SweepTree *>(insNode->elem[RIGHT]);
+
+ // start moving to the right now
+ while (insertR)
+ {
+ if (insertR->src == src)
+ {
+ if (insertR->src->getEdge(insertR->bord).st != fromPt
+ && insertR->src->getEdge(insertR->bord).en != fromPt)
+ {
+ break; // is the right edge not really attached to fromPt at all? so break
+ }
+ }
+ else
+ {
+ int ils = insertR->src->getEdge(insertR->bord).st;
+ int ile = insertR->src->getEdge(insertR->bord).en;
+ if ((insertR->src->pData[ils].rx[0] != fromP[0]
+ || insertR->src->pData[ils].rx[1] != fromP[1])
+ && (insertR->src->pData[ile].rx[0] != fromP[0]
+ || insertR->src->pData[ile].rx[1] != fromP[1]))
+ {
+ break;
+ }
+ }
+ // the new reference vector that we wanna compare to
+ bNorm = insertR->src->getEdge(insertR->bord).dx;
+ if (insertR->src->getEdge(insertR->bord).st >
+ insertR->src->getEdge(insertR->bord).en)
+ {
+ bNorm = -bNorm;
+ }
+ ang = cross(bNorm, nNorm);
+ if (ang > 0) // oh the edge goes to the left? so we just break since insertL and insertR are perfect then
+ {
+ break;
+ }
+ insertL = insertR; // go further to the right
+ insertR = static_cast<SweepTree *>(insertL->elem[RIGHT]);
+ }
+ }
+
+ int insertion = found_between; // by default set to found_between
+
+ if (insertL == nullptr) { // if nothing to left, it's found_on_left
+ insertion = found_on_left;
+ }
+ if (insertR == nullptr) { // if nothing on right, it's found_on_right
+ insertion = found_on_right;
+ }
+
+ if (insertion == found_exact) {
+ /* FIXME: surely this can never be called? */ // yea never called it looks like :P
+ if (insertR) {
+ insertR->RemoveEvent(queue, LEFT);
+ }
+ if (insertL) {
+ insertL->RemoveEvent(queue, RIGHT);
+ }
+ } else if (insertion == found_between) { // if found_between we do clear any events associated to the two nodes who are now no longer gonna be adjacent
+ insertR->RemoveEvent(queue, LEFT);
+ insertL->RemoveEvent(queue, RIGHT);
+ }
+
+ // let the parent do the actual insertion stuff in the tree now
+ AVLTree *tempR = static_cast<AVLTree *>(list.racine);
+ int err =
+ AVLTree::Insert(tempR, insertion, static_cast<AVLTree *>(insertL),
+ static_cast<AVLTree *>(insertR), rebalance);
+ list.racine = static_cast<SweepTree *>(tempR);
+ return err;
+}
+
+void
+SweepTree::Relocate(SweepTree * to)
+{
+ if (this == to)
+ return;
+ AVLTree::Relocate(to);
+ to->src = src;
+ to->bord = bord;
+ to->sens = sens;
+ to->evt[LEFT] = evt[LEFT];
+ to->evt[RIGHT] = evt[RIGHT];
+ to->startPoint = startPoint;
+ if (unsigned(bord) < src->swsData.size())
+ src->swsData[bord].misc = to;
+ if (unsigned(bord) < src->swrData.size())
+ src->swrData[bord].misc = to;
+ if (evt[LEFT])
+ evt[LEFT]->sweep[RIGHT] = to;
+ if (evt[RIGHT])
+ evt[RIGHT]->sweep[LEFT] = to;
+}
+
+// TODO check if ignoring these parameters is bad
+void
+SweepTree::SwapWithRight(SweepTreeList &/*list*/, SweepEventQueue &/*queue*/)
+{
+ SweepTree *tL = this;
+ SweepTree *tR = static_cast<SweepTree *>(elem[RIGHT]);
+
+ tL->src->swsData[tL->bord].misc = tR;
+ tR->src->swsData[tR->bord].misc = tL;
+
+ {
+ Shape *swap = tL->src;
+ tL->src = tR->src;
+ tR->src = swap;
+ }
+ {
+ int swap = tL->bord;
+ tL->bord = tR->bord;
+ tR->bord = swap;
+ }
+ {
+ int swap = tL->startPoint;
+ tL->startPoint = tR->startPoint;
+ tR->startPoint = swap;
+ }
+ //{double swap=tL->invDirLength;tL->invDirLength=tR->invDirLength;tR->invDirLength=swap;}
+ {
+ bool swap = tL->sens;
+ tL->sens = tR->sens;
+ tR->sens = swap;
+ }
+}
+
+void
+SweepTree::Avance(Shape */*dstPts*/, int /*curPoint*/, Shape */*a*/, Shape */*b*/)
+{
+ return;
+/* if ( curPoint != startPoint ) {
+ int nb=-1;
+ if ( sens ) {
+// nb=dstPts->AddEdge(startPoint,curPoint);
+ } else {
+// nb=dstPts->AddEdge(curPoint,startPoint);
+ }
+ if ( nb >= 0 ) {
+ dstPts->swsData[nb].misc=(void*)((src==b)?1:0);
+ int wp=waitingPoint;
+ dstPts->eData[nb].firstLinkedPoint=waitingPoint;
+ waitingPoint=-1;
+ while ( wp >= 0 ) {
+ dstPts->pData[wp].edgeOnLeft=nb;
+ wp=dstPts->pData[wp].nextLinkedPoint;
+ }
+ }
+ startPoint=curPoint;
+ }*/
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/livarot/sweep-tree.h b/src/livarot/sweep-tree.h
new file mode 100644
index 0000000..c4c84a5
--- /dev/null
+++ b/src/livarot/sweep-tree.h
@@ -0,0 +1,326 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * TODO: insert short description here
+ *//*
+ * Authors: see git history
+ *
+ * Copyright (C) 2018 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+#ifndef INKSCAPE_LIVAROT_SWEEP_TREE_H
+#define INKSCAPE_LIVAROT_SWEEP_TREE_H
+
+#include "livarot/AVL.h"
+#include <2geom/point.h>
+
+class Shape;
+class SweepEvent;
+class SweepEventQueue;
+class SweepTreeList;
+
+
+/**
+ * One node in the AVL tree of edges.
+ * Note that these nodes will be stored in a dynamically allocated array, hence the Relocate() function.
+ */
+
+/**
+ * A node in the sweep tree. For details about the sweep tree, what it is, what we do with it,
+ * why it's needed, check out SweepTreeList's documentation.
+ *
+ * Explanation of what is stored in evt and why:
+ * Say you have two edges in the sweepline `left` and `right` and an intersection is detected between
+ * the two. An intersection event (of type SweepEvent) is created and that event object stores
+ * pointer to the `left` and `right` edges (of type SweepTree). The left edge's evt[RIGHT]/evt[1]
+ * stores the pointer to the intersection event and the right edge's evt[LEFT]/evt[0] also stores
+ * it. This is done for a very important reason. If any point in time, either the LEFT or the RIGHT
+ * edge have to change their position in the sweepline for any reason at all (before the
+ * intersection point comes), we need to immediately delete that event from our list, cuz the edges
+ * are no longer together.
+ */
+class SweepTree:public AVLTree
+{
+public:
+ SweepEvent *evt[2]; /*!< Intersection with the edge on the left and right (if any). */
+
+ Shape *src; /*!< Shape from which the edge comes. (When doing boolean operation on polygons,
+ edges can come from 2 different polygons.) */
+ int bord; /*!< Edge index in the Shape. */
+ bool sens; /*!< true = top->bottom; false = bottom->top. */
+ int startPoint; /*!< point index in the result Shape associated with the upper end of the edge */
+
+ SweepTree();
+ ~SweepTree() override;
+
+ // Inits a brand new node.
+
+ /**
+ * Does what usually a constructor does.
+ *
+ * @param iSrc The pointer to the shape from which this edge comes from.
+ * @param iBord The edge index in the shape.
+ * @param iWeight The weight of the edge. Used along with edge's orientation to determine
+ * sens.
+ * @param iStartPoint Point index in the *result* Shape associated with the upper end of the
+ * edge
+ */
+ void MakeNew(Shape *iSrc, int iBord, int iWeight, int iStartPoint);
+ // changes the edge associated with this node
+ // goal: reuse the node when an edge follows another, which is the most common case
+
+ /**
+ * Reuse this node by just changing the variables.
+ *
+ * This is useful when you have one edge ending at a point and another one starting at the
+ * same point. So instead of deleting one and adding another at exactly the same location,
+ * you can just reuse the old one and change the variables.
+ *
+ * @param iSrc The pointer to the shape from which this edge comes from.
+ * @param iBord The edge index in the shape.
+ * @param iWeight The weight of the edge. Used along with edge's orientation to determine
+ * sens.
+ * @param iStartPoint Point index in the *result* Shape associated with the upper end of the
+ * edge
+ */
+ void ConvertTo(Shape *iSrc, int iBord, int iWeight, int iStartPoint);
+
+ // Delete the contents of node.
+
+ /**
+ * Delete this node. Make sure to change the pointers in any intersection event (that points to
+ * this node).
+ */
+ void MakeDelete();
+
+ // utilites
+
+ // the find function that was missing in the AVLTrree class
+ // the return values are defined in LivarotDefs.h
+
+ /**
+ * Find where the new edge needs to go in the sweepline tree.
+ *
+ * Please check out the documentation of the other version of Find that takes
+ * a point as input, this function is exactly identical except one block of code.
+ *
+ * For that special block of code, see this picture.
+ *
+ * @image html livarot-images/find-point-same.svg
+ *
+ * The rest of the function is the same as the other Find function so you're already
+ * familiar with how it works. The difference is how the y == 0 case is handled. When we are
+ * within that block, it's established that the upper endpoint of the new edge is on our edge.
+ * Now we see how the rest of the edge is orientated with respect to our edge so we can decide
+ * where to put it. To do this, we get the edge vector of the new edge and make sure it's top
+ * to bottom or if horizontal left to right. Then we rotate this new edge vector
+ * counter-clockwise by 90 degrees and shift it so that it starts at the same point as
+ * bNorm.ccw() does. The picture takes two cases simultaneously, one with edge red and the
+ * other being edge magenta. Their normals are shown with dotted lines and the shifted versions
+ * are lighter in color.
+ *
+ * Now if sweepSens is false, we take a cross product of new edge's normal with this edge's
+ * normal or cross(nNorm, bNorm). To figure out the direction of a cross product in SVG
+ * coordinates use this variation of right hand rule. Let index finger point to vector A. Let
+ * middle finger point to vector B. If thumb points out of page, cross product is negative, if
+ * it points inside the page, cross product is positive. Now you can see how when sweepSens is
+ * false, the cross product checks out with the orientation of the edges. If the cross product
+ * turns out to be zero, then we take dot product and let that decide. TODO: Which orientation
+ * will do what though?
+ *
+ * What about the other condition of sweepSens? When would sweepSens be true and how would that
+ * be useful. I think it has to do with sweeping in the opposite direction as a comment in the
+ * function already says.
+ *
+ * @image html livarot-images/find-point-same-opp-sweepsense.svg
+ *
+ * In this image, you can see the edges go bottom to top and then, you'd need cross(bNorm,
+ * nNorm) to figure out the correct orientation.
+ *
+ * @param iPt The point whose location we need to find in the sweepline.
+ * @param newOne The new edge that we wanna insert. To which point iPt belongs.
+ * @param insertL The edge that should go on the left (looking from the position where the new
+ * edge should go). This is set by the function.
+ * @param insertR The edge that should go on the right (looking form the position where the new
+ * edge should go). This is set by the function.
+ * @param sweepSens TODO: Why is this set to true? When it should be false when coming from
+ * ConvertToShape?
+ */
+ int Find(Geom::Point const &iPt, SweepTree *newOne, SweepTree *&insertL,
+ SweepTree *&insertR, bool sweepSens = true);
+
+ /**
+ * Find the place for a point (not an edge) in the sweepline tree.
+ *
+ * @image html livarot-images/find-point.svg
+ *
+ * To learn the algorithm, check the comments in the function body while referring back to
+ * the picture shown above. A brief summary follows.
+ *
+ * We start by taking our edge vector and if it goes bottom to top, or is horizontal and goes
+ * right to left, we flip its direction. In the picture bNorm shows this edge vector after any
+ * flipping. We rotate bNorm by 90 degrees counter-clockwise to get the normal vector.
+ * Then we take the start point of the edge vector (the original start point not the
+ * one after flipping) and draw a vector from the start point to the point whose position we
+ * are trying to find (iPt), we call this the diff vector. In the picture I have drawn these
+ * for three points red, blue and green. Now we take the dot product of this diff with the
+ * normal vectors. As you would now, a dot product has the formula:
+ * \f[ \vec{A}\cdot\vec{B} = |\vec{A}||\vec{B}|\cos\theta \f]
+ * \f$ \theta \f$ here is the angle between the two. As you would know, \f$ \cos \f$ is
+ * positive as long as the angle is between +90 and -90. At 90 degrees it becomes zero and
+ * greater than 90 or smaller than -90 it becomes negative. Thus the sign of the dot product
+ * can be used as an indicator of the angle between the normal vector and the diff vector.
+ * If this angle is within 90 to -90, it means the point lies to the right of the original
+ * edge. If it lies on 90 or -90, it means the point lies on the same line as the edge and
+ * if it's greater than 90 or smaller than -90, the point lies on the left side of the
+ * original edge.
+ *
+ * One thing to note, the blue point here is kinda wrong. You can't have another edge starting
+ * above the already existing edge when sweeping done (that's just not possible). So sorry
+ * about that. But the math checks out anyways. TODO: Maybe fix this to avoid confusion?
+ *
+ * One important point to see here is that the edge vector will be flipped however it's start
+ * point remains the same as the original one, this is not a problem as you can see in the
+ * image below. I chose the other point as the start point and everything works out the same.
+ *
+ * @image html livarot-images/find-point-2.svg
+ *
+ * I changed the starting point and redrew the diff vectors. Then I projected them such that
+ * their origin is the same as that of the normal. Measuring the angles again, everything
+ * remains the same.
+ *
+ * There is one more confusion part you'd find in this function. The part where left and right
+ * child are checked and you'd see child as well as elem pointers being used.
+ *
+ * @image html livarot-images/sweep-tree.svg
+ *
+ * The picture above shows you a how the sweepline tree structure looks like. I've used numbers
+ * instead of actual edges just for the purpose of illustration. The structure is an AVL tree
+ * as well as double-linked list. The nodes are arranged in a tree structure that balances
+ * itself but each node has two more points elem[LEFT] and elem[RIGHT] that can be used to
+ * navigate the linked list. In reality, the linked list structure is what's needed, but having
+ * an AVL tree makes searching really easy.
+ *
+ * See the comments in the if blocks in the function body. I've given examples from this
+ * diagram to explain stuff.
+ *
+ * @param iPt The point whose position we are trying to find.
+ * @param insertL The edge on the left (from the location where this point is supposed to go)
+ * @param insertR The edge on the right (from the location where this point is supposed to go)
+ *
+ * @return The found_* codes from LivarotDefs.h. See the file to learn about them.
+ */
+ int Find(Geom::Point const &iPt, SweepTree *&insertL, SweepTree *&insertR);
+
+ /// Remove sweepevents attached to this node.
+
+ /**
+ * Remove any events attached to this node.
+ *
+ * Since the event the other node referring to this event will also have it's
+ * evt value cleared.
+ *
+ * @param queue Reference to the event queue.
+ */
+ void RemoveEvents(SweepEventQueue &queue);
+
+ /**
+ * Remove event on the side s if it exists from event queue.
+ *
+ * Since the event the other node referring to this event will also have it's
+ * evt value cleared.
+ *
+ * @param queue Reference to the event queue.
+ * @param s The side to remove the event from.
+ */
+ void RemoveEvent(SweepEventQueue &queue, Side s);
+
+ // overrides of the AVLTree functions, to account for the sorting in the tree
+ // and some other stuff
+ int Remove(SweepTreeList &list, SweepEventQueue &queue, bool rebalance = true);
+
+ /**
+ * Insert this node at it's appropriate position in the sweepline tree.
+ *
+ * The function works by calling the Find function to let it find the appropriate
+ * position where this node should go, which it does by traversing the whole search
+ * tree.
+ *
+ * @param list A reference to the sweepline tree.
+ * @param queue A reference to the event queue.
+ * @param iDst Pointer to the shape to which this edge belongs to.
+ * @param iAtPoint The point at which we are adding this edge.
+ * @param rebalance TODO: Confirm this but most likely has to do with whether AVL should
+ * rebalance or not.
+ * @param sweepSens TODO: The same variable as in Find, has to do with sweepline direction.
+ */
+ int Insert(SweepTreeList &list, SweepEventQueue &queue, Shape *iDst,
+ int iAtPoint, bool rebalance = true, bool sweepSens = true);
+
+ /**
+ * Insert this node near an existing node.
+ *
+ * This is a simplification to the other Insert function. The normal Insert function would
+ * traverse the whole tree to find an appropriate place to add the current node. There are
+ * situations where we just added an edge and we have more edges connected to the same point
+ * that we wanna add. This function can be used to directly traverse left and right around
+ * the existing node to see where this edge would fit. Saves us full Find call. This searching
+ * is exactly how you'd insert something in a doubly-linked list. The function uses cross
+ * products to see where this edge should go relative to the existing one and then has loops
+ * to find the right spot for it.
+ *
+ * @image html livarot-images/find-point.svg
+ *
+ * I'll use this image to explain some stuff in the code body.
+ *
+ * @param list A reference to the sweepline tree.
+ * @param queue A reference to the event queue.
+ * @param iDst Pointer to the shape to which this edge belongs to.
+ * @param insNode Pointer to the node near which this is to be added.
+ * @param fromPt The point at which we are adding this edge.
+ * @param rebalance TODO: Confirm this but most likely has to do with whether AVL should
+ * rebalance or not.
+ * @param sweepSens TODO: The same variable as in Find, has to do with sweepline direction.
+ */
+ int InsertAt(SweepTreeList &list, SweepEventQueue &queue, Shape *iDst,
+ SweepTree *insNode, int fromPt, bool rebalance = true, bool sweepSens = true);
+
+ /// Swap nodes, or more exactly, swap the edges in them.
+
+ /**
+ * Used to swap two nodes with each other. Basically the data in the nodes are swapped
+ * not their addresses or locations in memory.
+ *
+ * Hence anyone referencing these nodes will get invalid (or unexpected) references since
+ * the data got swapped out. Therefore you must clear any events that might have references to
+ * these nodes.
+ *
+ * @param list Reference to the sweepline tree. Useless parameter.
+ * @param queue Reference to the event queue. Useless parameter.
+ */
+ void SwapWithRight(SweepTreeList &list, SweepEventQueue &queue);
+
+ /**
+ * Useless function. No active code in the function body. I suspected this became
+ * useless after Shape::Avance was implemented.
+ */
+ void Avance(Shape *dst, int nPt, Shape *a, Shape *b);
+
+ /**
+ * TODO: Probably has to do with some AVL relocation. Only called once from Node removal code.
+ */
+ void Relocate(SweepTree *to);
+};
+
+
+#endif /* !INKSCAPE_LIVAROT_SWEEP_TREE_H */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :