splivarot.cpp revision 93a2ae85bafa014d78a3b2a331d3bf36e67c3645
/*
* 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 <cstring>
#include <string>
#include <vector>
#include <glib.h>
#include "sp-path.h"
#include "sp-shape.h"
#include "sp-image.h"
#include "sp-marker.h"
#include "enums.h"
#include "sp-text.h"
#include "sp-flowtext.h"
#include "text-editing.h"
#include "sp-item-group.h"
#include "style.h"
#include "document.h"
#include "document-undo.h"
#include "layer-model.h"
#include "message-stack.h"
#include "selection.h"
#include "desktop.h"
#include "display/canvas-bpath.h"
#include "preferences.h"
#include "xml/repr-sorting.h"
#include "splivarot.h"
#include "verbs.h"
#include "2geom/svg-path-parser.h" // to get from SVG on boolean to Geom::Path
using Inkscape::DocumentUndo;
void sp_selected_path_boolop(Inkscape::Selection *selection, SPDesktop *desktop, bool_op bop, const unsigned int verb=SP_VERB_NONE, const Glib::ustring description="");
void
{
}
void
{
}
void
{
sp_selected_path_boolop(selection, desktop, bool_op_inters, SP_VERB_SELECTION_INTERSECT, _("Intersection"));
}
void
{
}
void
{
}
void
{
sp_selected_path_boolop(selection, desktop, bool_op_symdiff, SP_VERB_SELECTION_SYMDIFF, _("Exclusion"));
}
void
{
}
void
{
}
// helper for printing error messages, regardless of whether we have a GUI or not
// If desktop == NULL, errors will be shown on stderr
static void
{
if (desktop) {
} else {
}
}
// boolean operations PathVectors A,B -> PathVector result.
// This is derived from sp_selected_path_boolop
// take the source paths from the file, do the operation, delete the originals and add the results
// fra,fra are fill_rules for PathVectors a,b
sp_pathvector_boolop(Geom::PathVector const &pathva, Geom::PathVector const &pathvb, bool_op bop, fill_typ fra, fill_typ frb)
{
// extract the livarot Paths from the source objects
// also get the winding rule specified in the style
int nbOriginaux = 2;
// Livarot's outline of arcs is broken. So convert the path to linear and cubics only, for which the outline is created correctly.
// 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
} 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;
delete originaux[0];
delete originaux[1];
delete res;
return outres;
}
/* Convert from a livarot path to a 2geom PathVector */
return outres;
}
// boolean operations on the desktop
// take the source paths from the file, do the operation, delete the originals and add the results
void
sp_selected_path_boolop(Inkscape::Selection *selection, SPDesktop *desktop, bool_op bop, const unsigned int verb, const Glib::ustring description)
{
// allow union on a single object for the purpose of removing self overlapse (svn log, revision 13334)
boolop_display_error_message(desktop, _("Select <b>at least 2 paths</b> to perform a boolean operation."));
return;
}
boolop_display_error_message(desktop, _("Select <b>at least 1 path</b> to perform a boolean union."));
return;
}
boolop_display_error_message(desktop, _("Select <b>exactly 2 paths</b> to perform difference, 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;
// check in the tree to find which element of the selection list is topmost (for 2-operand commands only)
boolop_display_error_message(desktop, _("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
boolop_display_error_message(desktop, _("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
{
{
boolop_display_error_message(desktop, _("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;
{
// apply live path effects prior to performing boolean operation
if (SP_IS_LPE_ITEM(l->data)) {
}
} 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;
/* Due to quantization of the input shape coordinates, we may end up with A or B being empty.
* If this is a union or symdiff operation, we just use the non-empty shape as the result:
* A=0 => (0 or B) == B
* B=0 => (A or 0) == A
* A=0 => (0 xor B) == B
* B=0 => (A xor 0) == A
* If this is an intersection operation, we just use the empty shape as the result:
* A=0 => (0 and B) == 0 == A
* B=0 => (A and 0) == 0 == B
* If this a difference operation, and the upper shape (A) is empty, we keep B.
* If the lower shape (B) is empty, we still keep B, as it's empty:
* A=0 => (B - 0) == B
* B=0 => (0 - A) == 0 == B
*
* In any case, the output from this operation is stored in shape A, so we may apply
* the above rules simply by judicious use of swapping A and B where necessary.
*/
// We might need to do a swap. Apply the above rules depending on operation type.
|| (bop == bool_op_diff);
if (resultIsB) {
// Swap A and B to use B as the result
}
} else {
// Just do the Boolean operation as usual
// 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
{
}
if ((originaux[1]->pts.size() == 2) && originaux[1]->pts[0].isMoveTo && !originaux[1]->pts[1].isMoveTo)
else
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 (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 (title) {
}
if (desc) {
}
}
if (verb != SP_VERB_NONE) {
}
delete res;
}
static
{
if (!marker_item) {
return;
}
}
// total marker transform
if (marker_item->getRepr()) {
}
}
static
void item_outline_add_marker_child( SPItem const *item, Geom::Affine marker_transform, Geom::PathVector* pathv_in )
{
// note: a marker child item can be an item group!
if (SP_IS_GROUP(item)) {
// recurse through all childs:
if ( SP_IS_ITEM(o) ) {
}
}
} else {
if (marker_pathv) {
for (unsigned int j=0; j < marker_pathv->size(); j++) {
}
delete marker_pathv;
}
}
}
static
{
}
// total marker transform
SPItem const * marker_item = sp_item_first_item_child(marker_object); // why only consider the first item? can a marker only consist of a single item (that may be a group)?
if (marker_item) {
}
}
/**
* Returns a pathvector that is the outline of the stroked item, with markers.
* item must be SPShape or SPText.
*/
{
return ret_pathv;
}
// no stroke: no outline
return ret_pathv;
}
if (SP_IS_SHAPE(item)) {
} else if (SP_IS_TEXT(item)) {
}
return ret_pathv;
}
return ret_pathv;
}
// remember old stroke style, to be set on fill
// This may result in rounding errors for very small stroke widths (happens e.g. when user unit is large).
// See bug lp:1244861
}
{
case SP_STROKE_LINEJOIN_MITER:
break;
case SP_STROKE_LINEJOIN_ROUND:
o_join = join_round;
break;
default:
break;
}
case SP_STROKE_LINECAP_SQUARE:
break;
case SP_STROKE_LINECAP_ROUND:
o_butt = butt_round;
break;
default:
break;
}
}
// Livarot's outline of arcs is broken. So convert the path to linear and cubics only, for which the outline is created correctly.
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);
if (!bbox_only) {
delete theRes;
}
delete theShape;
} else {
if (!bbox_only) {
delete theShape;
delete theRes;
}
}
// ca a merd, ou bien le resultat est vide
delete res;
delete orig;
return ret_pathv;
}
// START marker
for (int i = 0; i < 2; i++) { // SP_MARKER_LOC and SP_MARKER_LOC_START
ret_pathv );
}
}
// MID marker
for (int i = 0; i < 3; i += 2) { // SP_MARKER_LOC and SP_MARKER_LOC_MID
if (!midmarker_obj) continue;
// START position
&& ! ((path_it == (pathv.end()-1)) && (path_it->size_default() == 0)) ) // if this is the last path and it is a moveto-only, there is no mid marker there
{
ret_pathv );
}
// MID position
{
/* Put marker between curve_it1 and curve_it2.
* Loop to end_default (so including closing segment), because when a path is closed,
* there should be a midpoint marker between last segment and closing straight line segment
*/
++curve_it1;
++curve_it2;
}
}
// END position
ret_pathv );
}
}
}
// END marker
for (int i = 0; i < 4; i += 3) { // SP_MARKER_LOC and SP_MARKER_LOC_END
/* Get reference to last curve in the path.
* For moveto-only path, this returns the "closing line segment". */
if (index > 0) {
index--;
}
ret_pathv );
}
}
}
}
delete res;
delete orig;
return ret_pathv;
}
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;
}
continue;
}
// pas de stroke pas de chocolat
continue;
}
// remember old stroke style, to be set on fill
{
if ( s_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.032)
o_width = 0.032;
}
continue;
}
// Livarot's outline of arcs is broken. So convert the path to linear and cubics only, for which the outline is created correctly.
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
// remember title
// remember description
// restore old style, but set old stroke style on fill
if (mask)
if (clip_path)
// add the group to the parent
// move to the saved position
// restore title, description, id, transform
if (title) {
}
if (desc) {
}
// START marker
for (int i = 0; i < 2; i++) { // SP_MARKER_LOC and SP_MARKER_LOC_START
}
}
// MID marker
for (int i = 0; i < 3; i += 2) { // SP_MARKER_LOC and SP_MARKER_LOC_MID
if (!midmarker_obj) continue;
// START position
&& ! ((path_it == (pathv.end()-1)) && (path_it->size_default() == 0)) ) // if this is the last path and it is a moveto-only, there is no mid marker there
{
}
// MID position
{
/* Put marker between curve_it1 and curve_it2.
* Loop to end_default (so including closing segment), because when a path is closed,
* there should be a midpoint marker between last segment and closing straight line segment
*/
++curve_it1;
++curve_it2;
}
}
// END position
}
}
}
// END marker
for (int i = 0; i < 4; i += 3) { // SP_MARKER_LOC and SP_MARKER_LOC_END
/* Get reference to last curve in the path.
* For moveto-only path, this returns the "closing line segment". */
if (index > 0) {
index--;
}
}
}
//bug lp:1290573 : completely destroy the old object first
item->deleteObject(false);
} else
{
//lp:1290573
item->deleteObject(false);
// add the new repr to the parent
// move to the saved position
// restore title, description, id, transform
if (title) {
}
if (desc) {
}
}
}
if (title) {
title = 0;
}
if (desc) {
desc = 0;
}
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
{
}
void
{
}
void
{
}
void
{
}
{
sp_selected_path_create_offset_object(desktop, 0, false);
}
{
}
{
}
{
sp_selected_path_create_offset_object(desktop, 0, true);
}
{
}
{
}
{
desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("Selected object is <b>not a path</b>, cannot inset/outset."));
return;
}
else if (SP_IS_SHAPE(item))
{
}
else // Item must be SP_TEXT
{
}
return;
}
//XML Tree being used directly here while it shouldn't be...
// remember the position of the item
// remember parent
float o_width = 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 ));
str = 0;
if ( updating ) {
//XML Tree being used directly here while it shouldn't be
} else {
}
// add the new repr to the parent
// move to the saved position
if ( !updating ) {
// delete original, apply the transform to the offset
item->deleteObject(false);
}
// 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=)
nitem->updateRepr();
}
(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;
else if (SP_IS_SHAPE(item)) {
}
else { // Item must be SP_TEXT
}
continue;
float o_width = 0;
float o_miter = 0;
//ButtType o_butt = butt_straight;
{
switch (jointype) {
case SP_STROKE_LINEJOIN_MITER:
break;
case SP_STROKE_LINEJOIN_ROUND:
o_join = join_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
item->deleteObject(false);
// 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
static 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);
}
// get path to simplify (note that the path *before* LPE calculation is needed)
return false;
}
// correct virtual size by full transform (bug #166937)
// 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
*/
// remember the position of the item
// remember parent
// remember id
// remember path effect
// remember title
// remember description
//If a group was selected, to not change the selection list
if (modifySelection) {
}
item->deleteObject(false);
if ( justCoalesce ) {
} else {
}
// restore style, mask and clip-path
if ( mask ) {
}
if ( clip_path ) {
}
// restore path effect
// path
if (patheffect)
else
// restore id
// add the new repr to the parent
// move to the saved position
// reapply the transform
// restore title & description
if (title) {
}
if (desc) {
}
//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)
{
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) {
}
}
if (pathsSimplified > 20) {
desktop->messageStack()->flashF(Inkscape::NORMAL_MESSAGE, _("<b>%d</b> paths simplified."), pathsSimplified);
}
return didSomething;
}
static 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
{
//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;
}
// derived from Path_for_item
// there must be some other way to load dest directly from epathv, without going through pathv...
Path *
{
delete pathv;
return dest;
}
Path *
{
return NULL;
Geom::PathVector *pathv = pathvector_for_curve(item, curve, doTransformation, transformFull, Geom::identity(), Geom::identity());
delete pathv;
return dest;
}
/**
* Obtains an item's Path before the LPE stack has been applied.
*/
Path *
{
return NULL;
Geom::PathVector *pathv = pathvector_for_curve(item, curve, doTransformation, transformFull, Geom::identity(), Geom::identity());
delete pathv;
return dest;
}
/*
* NOTE: Returns empty pathvector if curve == NULL
* TODO: see if calling this method can be optimized. All the pathvector copying might be slow.
*/
pathvector_for_curve(SPItem *item, SPCurve *curve, bool doTransformation, bool transformFull, Geom::Affine extraPreAffine, Geom::Affine extraPostAffine)
{
return NULL;
if (doTransformation) {
if (transformFull) {
} else {
}
} else {
}
return dest;
}
/**
* Obtains an item's curve. For SPPath, it is the path *before* LPE. For SPShapes other than path, it is the path *after* LPE.
* So the result is somewhat ill-defined, and probably this method should not be used... See curve_for_item_before_LPE.
*/
{
if (!item)
return NULL;
if (SP_IS_SHAPE(item)) {
if (SP_IS_PATH(item)) {
} else {
}
}
{
}
else if (SP_IS_IMAGE(item))
{
}
return curve; // do not forget to unref the curve at some point!
}
/**
* Obtains an item's curve *before* LPE.
* The returned SPCurve should be unreffed by the caller.
*/
{
if (!item)
return NULL;
if (SP_IS_SHAPE(item)) {
}
{
}
else if (SP_IS_IMAGE(item))
{
}
return curve; // do not forget to unref the curve at some point!
}
boost::optional<Path::cut_position> get_nearest_position_on_Path(Path *path, Geom::Point p, unsigned seg)
{
//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:fileencoding=utf-8:textwidth=99 :