splivarot.cpp revision f76f0d94b4b87bb6c1ba98e14d120aac94e415d0
#define __SP_LIVAROT_C__
/*
* Inkscape
*
* Created by fred on Fri Dec 05 2003.
* tweaked endlessly by bulia byak <buliabyak@users.sf.net>
* public domain
*
*/
/*
* contains lots of stitched pieces of path-chemistry.c
*/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <vector>
#include "sp-path.h"
#include "sp-shape.h"
#include "sp-image.h"
#include "marker.h"
#include "enums.h"
#include "sp-text.h"
#include "sp-item-group.h"
#include "style.h"
#include "inkscape.h"
#include "document.h"
#include "message-stack.h"
#include "selection.h"
#include "desktop-handles.h"
#include "desktop.h"
#include "display/canvas-bpath.h"
#include "prefs-utils.h"
#include "libnr/n-art-bpath.h"
#include "xml/repr-sorting.h"
#include <libnr/nr-matrix-fns.h>
#include <libnr/nr-matrix-ops.h>
#include <libnr/nr-matrix-translate-ops.h>
#include <libnr/nr-scale-matrix-ops.h>
#include "splivarot.h"
void sp_selected_path_boolop(bool_op bop, const unsigned int verb=SP_VERB_NONE, const Glib::ustring description="");
void
{
}
void
{
}
void
{
}
void
{
}
void
{
}
void
{
}
void
{
}
// boolean operations
// take the source paths from the file, do the operation, delete the originals and add the results
void
{
// allow union on a single object for the purpose of removing self overlapse (svn log, revision 13334)
desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("Select <b>at least 2 paths</b> to perform a boolean operation."));
return;
}
desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("Select <b>at least 1 path</b> to perform a boolean union."));
return;
}
desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("Select <b>exactly 2 paths</b> to perform difference, XOR, division, or path cut."));
return;
}
}
// reverseOrderForOp marks whether the order of the list is the top->down order
// it's only used when there are 2 objects, and for operations who need to know the
// topmost object (differences, cuts)
bool reverseOrderForOp = false;
// mettre les elements de la liste dans l'ordre pour ces operations
// check in the tree to find which element of the selection list is topmost (for 2-operand commands only)
desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("Unable to determine the <b>z-order</b> of the objects selected for difference, XOR, division, or path cut."));
return;
}
if (Ancetre(a, b)) {
// a is the parent of b, already in the proper order
} else if (Ancetre(b, a)) {
// reverse order
reverseOrderForOp = true;
} else {
// find their lowest common ancestor
desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("Unable to determine the <b>z-order</b> of the objects selected for difference, XOR, division, or path cut."));
return;
}
// find the children of the LCA that lead from it to the a and b
// find out which comes first
/* a first, so reverse. */
reverseOrderForOp = true;
break;
}
break;
}
}
}
// first check if all the input objects have shapes
// otherwise bail out
{
{
desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("One of the objects is <b>not a path</b>, cannot perform boolean operation."));
return;
}
}
// extract the livarot Paths from the source objects
// also get the winding rule specified in the style
int curOrig;
{
curOrig = 0;
{
} else {
}
{
return;
}
curOrig++;
}
}
// reverse if needed
// note that the selection list keeps its order
if ( reverseOrderForOp ) {
}
// and work
// some temporary instances, first
res->SetBackData(false);
int nbToCut=0;
if ( bop == bool_op_inters || bop == bool_op_union || bop == bool_op_diff || bop == bool_op_symdiff ) {
// true boolean op
// get the polygons of each path, with the winding rule specified, and apply the operation iteratively
curOrig = 1;
// les elements arrivent en ordre inverse dans la liste
{
}
curOrig++;
}
{
}
} else if ( bop == bool_op_cut ) {
// cuts= sort of a bastard boolean operation, thus not the axact same modus operandi
// technically, the cut path is not necessarily a polygon (thus has no winding rule)
// it is just uncrossed, and cleaned from duplicate edges and points
// then it's fed to Booleen() which will uncross it against the other path
// then comes the trick: each edge of the cut path is duplicated (one in each direction),
// thus making a polygon. the weight of the edges of the cut are all 0, but
// the Booleen need to invert the ones inside the source polygon (for the subsequent
// ConvertToForme)
// the cut path needs to have the highest pathID in the back data
// that's how the Booleen() function knows it's an edge of the cut
// FIXME: this gives poor results, the final paths are full of extraneous nodes. Decreasing
// ConvertWithBackData parameter below simply increases the number of nodes, so for now I
// left it at 1.0. Investigate replacing this by a combination of difference and
// intersection of the same two paths. -- bb
{
}
theShapeB->ConvertToShape(theShape, fill_justDont); // fill_justDont doesn't computes winding numbers
// les elements arrivent en ordre inverse dans la liste
} else if ( bop == bool_op_slice ) {
// slice is not really a boolean operation
// you just put the 2 shapes in a single polygon, uncross it
// the points where the degree is > 2 are intersections
// just check it's an intersection on the path you want to cut, and keep it
// the intersections you have found are then fed to ConvertPositionsToMoveTo() which will
// make new subpath at each one of these positions
// inversion pour l'opration
{
}
originaux[1]->Fill(theShapeA, 1,true,false,false);// don't closeIfNeeded and just dump in the shape, don't reset it
if ( theShape->hasBackData() ) {
// should always be the case, but ya never know
{
for (int i = 0; i < theShape->numberOfPoints(); i++) {
// possibly an intersection
// we need to check that at least one edge from the source path is incident to it
// before we declare it's an intersection
int nbOrig=0;
int nbOther=0;
int piece=-1;
float t=0.0;
// the source has an edge incident to the point, get its position on the path
} else {
}
nbOrig++;
}
}
// point incident to both path and cut: an intersection
// note that you only keep one position on the source; you could have degenerate
// cases where the source crosses itself at this point, and you wouyld miss an intersection
nbToCut++;
}
}
}
}
{
// i think it's useless now
for (;i>=0;i--) {
}
}
}
}
}
int nbNest=0;
// pour compenser le swap juste avant
if ( bop == bool_op_slice ) {
// theShape->ConvertToForme(res, nbOriginaux, originaux, true);
// res->ConvertForcedToMoveTo();
} else if ( bop == bool_op_cut ) {
// il faut appeler pour desallouer PointData (pas vital, mais bon)
// the Booleen() function did not deallocated the point_data array in theShape, because this
// function needs it.
// this function uses the point_data to get the winding number of each path (ie: is a hole or not)
// for later reconstruction in objects, you also need to extract which path is parent of holes (nesting info)
} else {
}
delete theShape;
delete theShapeA;
delete theShapeB;
for (int i = 0; i < nbOriginaux; i++) delete originaux[i];
{
// only one command, presumably a moveto: it isn't a path
{
}
delete res;
return;
}
// get the source path object
if ( bop == bool_op_diff || bop == bool_op_symdiff || bop == bool_op_cut || bop == bool_op_slice ) {
if (reverseOrderForOp) {
} else {
}
} else {
// find out the bottom object
}
// adjust style properties that depend on a possible transform in the source object in order
// to get a correct style attribute for the new path
// remember important aspects of the source path, to be restored
// remove source paths
// if this is the bottommost object,
// delete it so that its clones don't get alerted; this object will be restored shortly, with the same id
} else {
// delete the object for real, so that its clones can take appropriate action
}
}
// premultiply by the inverse of parent's repr
// now that we have the result, add it on the canvas
int nbRP=0;
if ( bop == bool_op_slice ) {
// there are moveto's at each intersection, but it's still one unique path
// so break it down and add each subpath independently
// we could call break_apart to do this, but while we have the description...
} else {
// cut operation is a bit wicked: you need to keep holes
// that's why you needed the nesting
// ConvertToFormeNested() dumped all the subpath in a single Path "res", so we need
// to get the path for each part of the polygon. that's why you need the nesting info:
// to know in wich subpath to add a subpath
// cleaning
}
// add all the pieces resulting from cut or slice
for (int i=0;i<nbRP;i++) {
if (mask)
if (clip_path)
g_free(d);
// for slice, remove fill
if (bop == bool_op_slice) {
css = sp_repr_css_attr_new();
}
// we assign the same id on all pieces, but it on adding to document, it will be changed on all except one
// this means it's basically random which of the pieces inherits the original's id and clones
// a better algorithm might figure out e.g. the biggest piece
// add the new repr to the parent
// move to the saved position
delete resPath[i];
}
} else {
if ( mask )
if ( clip_path )
g_free(d);
}
if (verb != SP_VERB_NONE) {
}
delete res;
}
void
{
desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>stroked path(s)</b> to convert stroke to path."));
return;
}
bool did = false;
continue;
if (SP_IS_SHAPE(item)) {
continue;
}
if (SP_IS_TEXT(item)) {
continue;
}
{ // pas de stroke pas de chocolat
continue;
}
}
// remember old stroke style, to be set on fill
{
ncss = sp_repr_css_attr_new();
if ( opac ) {
} else {
}
}
{
switch (jointype) {
case SP_STROKE_LINEJOIN_MITER:
break;
case SP_STROKE_LINEJOIN_ROUND:
o_join = join_round;
break;
default:
break;
}
switch (captype) {
case SP_STROKE_LINECAP_SQUARE:
break;
case SP_STROKE_LINECAP_ROUND:
o_butt = butt_round;
break;
default:
break;
}
if (o_width < 0.1)
o_width = 0.1;
}
continue;
}
res->SetBackData(false);
// For dashed strokes, use Stroke method, because Outline can't do dashes
// However Stroke adds lots of extra nodes _or_ makes the path crooked, so consider this a temporary workaround
0.5 * o_miter);
delete theShape;
delete theRes;
} else {
delete theShape;
delete theRes;
}
// ca a merd, ou bien le resultat est vide
delete res;
delete orig;
continue;
}
did = true;
// remember the position of the item
// remember parent
// remember id
// restore old style
// set old stroke style on fill
if (mask)
if (clip_path)
// add the group to the parent
// move to the saved position
for (int m = SP_MARKER_LOC_START; m < SP_MARKER_LOC_QTY; m++) {
}
// total marker transform
if (SP_OBJECT_REPR(marker_item)) {
}
}
}
}
} else {
// add the new repr to the parent
// move to the saved position
}
}
delete res;
delete orig;
}
if (did) {
_("Convert stroke to path"));
} else {
// TRANSLATORS: "to outline" means "to convert stroke to path"
desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("<b>No stroked paths</b> in the selection."));
return;
}
}
void
{
sp_selected_path_do_offset(true, prefOffset);
}
void
{
sp_selected_path_do_offset(false, prefOffset);
}
void
sp_selected_path_offset_screen(double pixels)
{
}
void
sp_selected_path_inset_screen(double pixels)
{
}
{
sp_selected_path_create_offset_object(0, false);
}
{
sp_selected_path_create_offset_object(1, false);
}
{
sp_selected_path_create_offset_object(-1, false);
}
{
sp_selected_path_create_offset_object(0, true);
}
{
sp_selected_path_create_offset_object(1, true);
}
{
sp_selected_path_create_offset_object(-1, true);
}
void
{
desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("Selected object is <b>not a path</b>, cannot inset/outset."));
return;
}
if (SP_IS_SHAPE(item))
{
return;
}
if (SP_IS_TEXT(item))
{
return;
}
// remember the position of the item
// remember parent
{
if (jointype == SP_STROKE_LINEJOIN_MITER)
{
}
else if (jointype == SP_STROKE_LINEJOIN_ROUND)
{
o_join = join_round;
}
else
{
}
if (captype == SP_STROKE_LINECAP_SQUARE)
{
}
else if (captype == SP_STROKE_LINECAP_ROUND)
{
o_butt = butt_round;
}
else
{
}
{
double prefOffset = 1.0;
}
if (o_width < 0.01)
o_width = 0.01;
}
{
return;
}
res->SetBackData(false);
{
{
}
{
}
else
{
}
delete theShape;
delete theRes;
}
{
// pas vraiment de points sur le resultat
// donc il ne reste rien
(updating ? _("Create linked offset")
: _("Create dynamic offset")));
delete res;
delete orig;
return;
}
{
? o_width
: expand < 0
? -o_width
: 0 ));
if ( updating ) {
} else {
}
// add the new repr to the parent
// move to the saved position
if ( updating ) {
// on conserve l'original
// we reapply the transform to the original (offset will feel it)
} else {
// delete original, apply the transform to the offset
}
// The object just created from a temporary repr is only a seed.
// We need to invoke its write which will update its real repr (in particular adding d=)
}
(updating ? _("Create linked offset")
: _("Create dynamic offset")));
delete res;
delete orig;
}
void
{
desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>path(s)</b> to inset/outset."));
return;
}
bool did = false;
continue;
if (SP_IS_SHAPE(item)) {
continue;
}
if (SP_IS_TEXT(item)) {
continue;
}
{
switch (jointype) {
case SP_STROKE_LINEJOIN_MITER:
break;
case SP_STROKE_LINEJOIN_ROUND:
o_join = join_round;
break;
default:
break;
}
switch (captype) {
case SP_STROKE_LINECAP_SQUARE:
break;
case SP_STROKE_LINECAP_ROUND:
o_butt = butt_round;
break;
default:
break;
}
if (o_width < 0.1)
o_width = 0.1;
}
continue;
}
res->SetBackData(false);
{
{
}
{
}
else
{
}
// et maintenant: offset
// methode inexacte
/* Path *originaux[1];
originaux[0] = orig;
theRes->ConvertToForme(res, 1, originaux);
if (expand) {
res->OutsideOutline(orig, 0.5 * o_width, o_join, o_butt, o_miter);
} else {
res->OutsideOutline(orig, -0.5 * o_width, o_join, o_butt, o_miter);
}
orig->ConvertWithBackData(1.0);
orig->Fill(theShape, 0);
theRes->ConvertToShape(theShape, fill_positive);
originaux[0] = orig;
theRes->ConvertToForme(res, 1, originaux);
if (o_width >= 0.5) {
// res->Coalesce(1.0);
res->ConvertEvenLines(1.0);
res->Simplify(1.0);
} else {
// res->Coalesce(o_width);
res->ConvertEvenLines(1.0*o_width);
res->Simplify(1.0 * o_width);
} */
// methode par makeoffset
if (expand)
{
}
else
{
}
if (o_width >= 1.0)
{
}
else
{
}
delete theShape;
delete theRes;
}
did = true;
// remember the position of the item
// remember parent
// remember id
// add the new repr to the parent
// move to the saved position
// reapply the transform
}
delete orig;
delete res;
}
if (did) {
} else {
desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("<b>No paths</b> to inset/outset in the selection."));
return;
}
}
static bool
float threshold, bool justCoalesce,
float angleLimit, bool breakableAngles,
bool modifySelection);
//return true if we changed something, else false
bool
float threshold, bool justCoalesce,
float angleLimit, bool breakableAngles,
{
return false;
//If this is a group, do the children instead
if (SP_IS_GROUP(item)) {
false);
}
if (SP_IS_SHAPE(item)) {
if (!curve)
return false;
}
if (SP_IS_TEXT(item)) {
if (!curve)
return false;
}
// save the transform, to re-apply it after simplification
/*
reset the transform, effectively transforming the item by transform.inverse();
this is necessary so that the item is transformed twice back and forth,
allowing all compensations to cancel out regardless of the preferences
*/
return false;
}
// remember the position of the item
// remember parent
// remember id
//If a group was selected, to not change the selection list
if (modifySelection)
if ( justCoalesce ) {
} else {
}
// restore style, mask and clip-path
if ( mask ) {
}
if ( clip_path ) {
}
// path
// restore id
// add the new repr to the parent
// move to the saved position
// reapply the transform
//If we are not in a selected group
if (modifySelection)
// clean up
return true;
}
bool
float threshold, bool justCoalesce,
float angleLimit, bool breakableAngles,
bool modifySelection)
{
bool simplifyIndividualPaths =
if (simplifyIndividualPaths) {
simplificationType = _("Simplifying paths (separately):");
} else {
simplificationType = _("Simplifying paths:");
}
bool didSomething = false;
if (!selectionBbox) {
return false;
}
int pathsSimplified = 0;
// set "busy" cursor
continue;
if (simplifyIndividualPaths) {
if (itemBbox) {
} else {
simplifySize = 0;
}
}
if (pathsSimplified % 20 == 0) {
gchar *message = g_strdup_printf(_("%s <b>%d</b> of <b>%d</b> paths simplified..."), simplificationType, pathsSimplified, totalPathCount);
}
}
if (pathsSimplified > 20) {
desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, g_strdup_printf(_("<b>%d</b> paths simplified."), pathsSimplified));
}
return didSomething;
}
void
float angleLimit, bool breakableAngles)
{
_("Select <b>path(s)</b> to simplify."));
return;
}
breakableAngles, true);
if (didSomething)
_("Simplify"));
else
desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("<b>No paths</b> to simplify in the selection."));
}
// globals for keeping track of accelerated simplify
static double previousTime = 0.0;
void
{
bool simplifyJustCoalesce =
//Get the current time
//Was the previous call to this function recent? (<0.5 sec)
// add to the threshold 1/2 of its original value
simplifyMultiply += 0.5;
} else {
// reset to the default
simplifyMultiply = 1;
}
//remember time for next call
//g_print("%g\n", simplify_threshold);
//Make the actual call
simplifyJustCoalesce, 0.0, false);
}
// fonctions utilitaires
bool
{
return false;
if (who == a)
return true;
}
Path *
{
if (!item)
return NULL;
if (SP_IS_SHAPE(item))
{
}
else if (SP_IS_TEXT(item))
{
}
else if (SP_IS_IMAGE(item))
{
}
else
{
}
if (!curve)
return NULL;
return NULL;
if ( doTransformation ) {
if (transformFull)
else
} else {
}
if ( doTransformation ) {
} else {
}
return dest;
}
dest->SetBackData(false);
{
int i;
bool closed = false;
float lastX = 0.0;
float lastY = 0.0;
case NR_LINETO:
{
}
break;
case NR_CURVETO:
{
}
break;
case NR_MOVETO_OPEN:
case NR_MOVETO:
if (closed)
{
}
break;
default:
break;
}
}
if (closed)
}
return dest;
}
{
//get nearest position on path
return pos;
}
{
return p;
}
/*
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:encoding=utf-8:textwidth=99 :