27#define noERASER_VERBOSE
36#include <gdk/gdkkeysyms.h>
37#include <glibmm/i18n.h>
75 , _break_apart{
"/tools/eraser/break_apart", false}
76 , _mode_int{
"/tools/eraser/mode", 1}
118 }
else if (mode_idx == 1) {
120 }
else if (mode_idx == 2) {
123 g_printerr(
"Error: invalid mode setting \"%d\" for Eraser tool!", mode_idx);
182 double const m = std::lerp(1.0, 160.0,
mass);
194 double angle_fixed{0.0};
203 angle_fixed =
angle * M_PI / 180.0;
214 bool flipped =
false;
215 if (fabs(angle_dynamic - angle_fixed) > M_PI_2) {
216 angle_dynamic += M_PI;
220 if (angle_dynamic > M_PI) {
221 angle_dynamic -= 2 * M_PI;
223 if (angle_dynamic < -M_PI) {
224 angle_dynamic += 2 * M_PI;
231 double new_ang = std::lerp(angle_dynamic, angle_fixed, fabs(
flatness)) - (flipped ? M_PI : 0);
236 if (angle_delta / speed > 4000) {
244 double const d = std::lerp(0.0, 0.5,
square(
drag));
259 double const vel_thinning = std::lerp(0, 160,
vel_thin);
267 double const trace_thick = 1;
269 double effective_width = (pressure_thick * trace_thick - vel_thinning * speed) *
width;
271 double tremble_left = 0, tremble_right = 0;
282 double const width_coefficient = 0.15 + 0.8 * effective_width;
283 double const speed_coefficient = 0.35 + 14 * speed;
284 double const total_coefficient =
tremor * width_coefficient * speed_coefficient;
286 tremble_left = y1 * total_coefficient;
287 tremble_right = y2 * total_coefficient;
290 double const min_width = 0.02 *
width;
291 if (effective_width < min_width) {
292 effective_width = min_width;
295 double dezoomify_factor = 0.05 * 1000;
300 Geom::Point del_left = dezoomify_factor * (effective_width + tremble_left) *
ang;
301 Geom::Point del_right = dezoomify_factor * (effective_width + tremble_right) *
ang;
319 x1 = 2.0 * g_random_double_range(0, 1) - 1.0;
320 x2 = 2.0 * g_random_double_range(0, 1) - 1.0;
323 w = sqrt(-2.0 *
log(
w) /
w);
348 if (event.num_press == 1 && event.button == 1) {
349 if (!have_viable_layer(_desktop, defaultMessageContext())) {
354 auto const button_w = event.pos;
368 rubberband->start(
_desktop, button_dt);
380 auto const motion_w =
event.pos;
381 auto const motion_dt = _desktop->w2d(motion_w);
382 _extinput(event.extinput);
384 message_context->clear();
386 if (is_drawing && (event.modifiers & GDK_BUTTON1_MASK)) {
391 if (!_apply(motion_dt)) {
398 g_assert(npoints > 0);
410 [&] (ButtonReleaseEvent
const &event) {
411 if (event.button != 1) {
415 auto const motion_w =
event.pos;
416 auto const motion_dt = _desktop->w2d(motion_w);
418 ungrabCanvasEvents();
433 auto document = _desktop->getDocument();
446 message_context->clear();
452 if (r->isStarted()) {
458 [&] (KeyPressEvent
const &event) {
459 ret = _handleKeypress(event);
462 [&] (KeyReleaseEvent
const &event) {
464 case GDK_KEY_Control_L:
465 case GDK_KEY_Control_R:
466 message_context->clear();
474 [&] (CanvasEvent
const &event) {}
484 bool just_ctrl = (
key.modifiers & GDK_CONTROL_MASK)
485 && !(
key.modifiers & (GDK_ALT_MASK | GDK_SHIFT_MASK));
487 bool just_alt = (
key.modifiers & GDK_ALT_MASK)
488 && !(
key.modifiers & (GDK_CONTROL_MASK | GDK_SHIFT_MASK));
492 case GDK_KEY_KP_Right:
499 _desktop->setToolboxAdjustmentValue(
"eraser-width",
width * 100);
505 case GDK_KEY_KP_Left:
511 _desktop->setToolboxAdjustmentValue(
"eraser-width",
width * 100);
517 case GDK_KEY_KP_Home:
519 _desktop->setToolboxAdjustmentValue(
"eraser-width",
width * 100);
526 _desktop->setToolboxAdjustmentValue(
"eraser-width",
width * 100);
533 _desktop->setToolboxFocusTo(
"eraser-width");
539 if (
mode == EraserToolMode::DELETE) {
551 if (just_ctrl && is_drawing) {
568 auto *top_layer = _desktop->layerManager().currentRoot();
569 auto *eraser_item = cast<SPItem>(top_layer->appendChildRepr(repr));
571 eraser_item->updateRepr();
573 pathv *= eraser_item->i2doc_affine().inverse();
578void EraserTool::_clearCurrent()
581 currentshape->set_bpath(
nullptr);
584 currentcurve.reset();
596bool EraserTool::_doWork()
598 if (accumulated.is_empty()) {
606 SPDocument *document = _desktop->getDocument();
619 Selection *selection = _desktop->getSelection();
623 bool was_selection = !selection->
isEmpty();
629 std::vector<EraseTarget> to_erase = _findItemsToErase();
631 bool work_done =
false;
632 if (!to_erase.empty()) {
634 work_done = _performEraseOperation(to_erase,
true);
635 if (was_selection && !_survivers.empty()) {
636 selection->
add(_survivers.begin(), _survivers.end());
652bool EraserTool::_cutErase(
EraseTarget target,
bool store_survivers)
655 if (
auto use = cast<SPUse>(target.
item)) {
656 auto original = use->trueOriginal();
657 if (_uncuttableItemType(
original)) {
659 _survivers.push_back(target.
item);
662 }
else if (
auto *group = cast<SPGroup>(
original)) {
663 return _probeUnlinkCutClonedGroup(target, use, group, store_survivers);
666 target.
item = use->unlink();
668 _survivers.push_back(target.
item);
671 return _booleanErase(target, store_survivers);
689 bool store_survivers)
691 std::vector<EraseTarget> children;
695 children.emplace_back(cast<SPItem>(
child),
false);
697 auto const filtered_children = _filterCutEraseables(children,
true);
702 if (
auto *parent_item = cast<SPItem>(cloned_group->
parent)) {
703 parent_inverse_transform = parent_item->i2doc_affine().
inverse();
705 auto const relative_transform = parent_inverse_transform * clone->i2doc_affine();
706 auto const eraser_bounds = _acid->documentExactBounds();
707 if (!eraser_bounds) {
710 auto const eraser_in_group_coordinates = *eraser_bounds * relative_transform.inverse();
711 bool found_collision =
false;
712 for (
auto const &orig_child : filtered_children) {
713 if (orig_child.item->collidesWith(eraser_in_group_coordinates)) {
714 found_collision =
true;
718 if (found_collision) {
719 auto *unlinked = cast<SPGroup>(clone->unlink());
723 std::vector<EraseTarget> unlinked_children;
724 unlinked_children.reserve(filtered_children.size());
726 for (
auto *
child : unlinked->childList(
false)) {
727 unlinked_children.emplace_back(cast<SPItem>(
child),
false);
729 auto overlapping = _filterCutEraseables(_filterByCollision(unlinked_children, _acid));
733 _survivers.push_back(unlinked);
736 return _performEraseOperation(overlapping,
false);
739 _survivers.push_back(original_target.
item);
741 if (filtered_children.size() < children.size()) {
742 auto non_eraseable_touched = [&](
EraseTarget const &t) ->
bool {
743 if (!t.item || !_uncuttableItemType(t.item)) {
746 return t.item->collidesWith(eraser_in_group_coordinates);
748 if (std::any_of(children.begin(), children.end(), non_eraseable_touched)) {
749 _setStatusBarMessage(_(
"Some objects could not be cut."));
761 }
else if (is<SPImage>(
item)) {
763 }
else if (_isStraightSegment(
item)) {
776bool EraserTool::_booleanErase(
EraseTarget target,
bool store_survivers)
783 repr->parent()->appendChild(duplicate_stroke);
784 Glib::ustring duplicate_id = duplicate_stroke->
attribute(
"id");
787 operands.
set(duplicate_stroke);
792 _handleStrokeStyle(target.
item);
801 if (
auto *spill = _desktop->doc()->getObjectById(duplicate_id)) {
803 spill->deleteObject(
false);
808 }
else if (!nowidth) {
812 _survivers.insert(_survivers.end(), operands.
items().begin(), operands.
items().end());
825bool EraserTool::_performEraseOperation(std::vector<EraseTarget>
const &items_to_erase,
bool store_survivers)
827 if (
mode == EraserToolMode::CUT) {
828 bool erased_something =
false;
829 for (
auto const &target : items_to_erase) {
830 erased_something = _cutErase(target, store_survivers) || erased_something;
832 return erased_something;
833 }
else if (
mode == EraserToolMode::CLIP) {
837 for (
auto const &target : items_to_erase) {
838 _clipErase(target.item);
842 for (
auto const &target : items_to_erase) {
844 target.item->deleteObject(
true);
865void EraserTool::_setStatusBarMessage(
char *message)
868 _our_messages.push_back(
id);
872void EraserTool::_clearStatusBar()
874 if (!_our_messages.empty()) {
875 auto ms = _desktop->messageStack();
879 _our_messages.clear();
890 repr->parent()->appendChild(dup);
892 w_selection.
set(dup);
894 bool delete_old_clip_path =
false;
897 std::vector<SPItem *> selected;
898 selected.push_back(cast<SPItem>(clip_path->
firstChild()));
899 std::vector<Inkscape::XML::Node *> to_select;
900 std::vector<SPItem *>
items(selected);
903 if (!clip_data && !to_select.empty()) {
904 clip_data = *(to_select.begin());
913 dup_clip_obj->updateRepr();
914 delete_old_clip_path =
true;
916 w_selection.
add(dup_clip);
926 rect->setPosition(bbox->left(), bbox->top(), bbox->width(), bbox->height());
927 rect->transform = cast<SPItem>(rect->parent)->i2doc_affine().inverse();
930 rect->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
932 w_selection.
add(rect);
937 w_selection.
setMask(
true,
false,
true);
938 if (delete_old_clip_path) {
945bool EraserTool::_isStraightSegment(
SPItem *path)
947 auto as_path = cast<SPPath>(path);
952 auto const &
curve = as_path->curve();
956 auto const &pathvector =
curve->get_pathvector();
959 for (
auto const &path : pathvector) {
960 Geom::Point initial_tangent = path.front().unitTangentAt(0.0);
961 for (
auto const &segment : path) {
962 if (!segment.isLineSegment()) {
984 if (mag_in > epsilon) {
985 v_in = mag * v_in / mag_in;
993 if (mag_out > epsilon) {
994 v_out = mag * v_out / mag_out;
1000 curve.curveto(from + v_in, to + v_out, to);
1004void EraserTool::_accumulate()
1008 if (!cal1.get_segment_count() || !cal2.get_segment_count()) {
1012 auto rev_cal2 = cal2.reversed();
1014 g_assert(!cal1.first_path()->closed());
1015 g_assert(!rev_cal2.first_path()->closed());
1022 g_assert(dc_cal1_firstseg);
1023 g_assert(rev_cal2_firstseg);
1024 g_assert(dc_cal1_lastseg);
1025 g_assert(rev_cal2_lastseg);
1027 accumulated.append(cal1);
1029 _addCap(accumulated,
1036 accumulated.append(rev_cal2,
true);
1038 _addCap(accumulated,
1045 accumulated.closepath();
1059std::vector<EraseTarget> EraserTool::_filterCutEraseables(std::vector<EraseTarget>
const &
items,
bool silent)
1061 std::vector<EraseTarget>
result;
1064 for (
auto &target :
items) {
1065 if (
Error e = _uncuttableItemType(target.item)) {
1067 if (e & RASTER_IMAGE) {
1068 _setStatusBarMessage(_(
"Cannot cut out from a bitmap, use <b>Clip</b> mode "
1070 }
else if (e & NO_AREA_PATH) {
1071 _setStatusBarMessage(_(
"Cannot cut out from a path with zero area, use "
1072 "<b>Clip</b> mode instead."));
1076 result.push_back(target);
1088std::vector<EraseTarget> EraserTool::_filterByCollision(std::vector<EraseTarget>
const &
items,
SPItem *with)
const
1090 std::vector<EraseTarget>
result;
1097 for (
auto const &target :
items) {
1098 if (target.item && target.item->collidesWith(*collision_shape)) {
1099 result.push_back(target);
1114std::vector<EraseTarget> EraserTool::_findItemsToErase()
1116 std::vector<EraseTarget>
result;
1118 auto *document = _desktop->getDocument();
1119 auto *selection = _desktop->getSelection();
1120 if (!document || !selection) {
1124 if (
mode == EraserToolMode::DELETE) {
1129 std::vector<SPItem *> touched = document->getItemsAtPoints(_desktop->dkey, r->getPoints());
1130 if (selection->isEmpty()) {
1131 for (
auto *
item : touched) {
1135 for (
auto *
item : selection->items()) {
1136 if (std::find(touched.begin(), touched.end(),
item) == touched.end()) {
1137 _survivers.push_back(
item);
1145 _acid = _insertAcidIntoDocument(document);
1153 std::vector<SPItem *> candidates = document->getItemsPartiallyInBox(_desktop->dkey, *eraser_bbox,
1154 false,
false,
false,
true);
1155 std::vector<EraseTarget> allowed;
1156 allowed.reserve(candidates.size());
1159 if (selection->isEmpty()) {
1160 for (
auto *candidate : candidates) {
1161 if (candidate != _acid) {
1162 allowed.emplace_back(candidate,
false);
1167 if (
mode == EraserToolMode::CUT) {
1170 for (
auto *selected : selection->items()) {
1171 bool included_for_erase =
false;
1172 for (
auto *candidate : candidates) {
1173 if (selected == candidate || selected->isAncestorOf(candidate)) {
1174 allowed.emplace_back(candidate, selection->includes(candidate));
1175 included_for_erase = (candidate == selected) || included_for_erase;
1178 if (!included_for_erase) {
1179 _survivers.push_back(selected);
1185 auto overlapping = _filterByCollision(allowed, _acid);
1186 auto valid = _filterCutEraseables(overlapping);
1188 for (
auto const &element : allowed) {
1189 if (element.item && element.was_selected &&
1190 std::find(valid.begin(), valid.end(), element) == valid.end())
1192 _survivers.push_back(element.item);
1195 result.insert(
result.end(), valid.begin(), valid.end());
1197 }
else if (
mode == EraserToolMode::CLIP) {
1199 auto const all_selected = selection->items();
1200 for (
auto *
item : all_selected) {
1201 allowed.emplace_back(
item,
true);
1207 auto overlapping = _filterByCollision(allowed, _acid);
1208 result.insert(
result.end(), overlapping.begin(), overlapping.end());
1209 _survivers.insert(_survivers.end(), all_selected.begin(), all_selected.end());
1215void EraserTool::_fitAndSplit(
bool releasing)
1217 double const tolerance_sq =
square(_desktop->w2d().descrim() * tolerance);
1218 nowidth = (
width == 0);
1220#ifdef ERASER_VERBOSE
1221 g_print(
"[F&S:R=%c]", releasing ?
'T' :
'F');
1228 _completeBezier(tolerance_sq, releasing);
1230#ifdef ERASER_VERBOSE
1231 g_print(
"[%d]Yup\n", npoints);
1234 _fitDrawLastPoint();
1238 point1[0] = point1[npoints - 1];
1239 point2[0] = point2[npoints - 1];
1242 _drawTemporaryBox();
1246void EraserTool::_completeBezier(
double tolerance_sq,
bool releasing)
1249 if (cal1.is_empty() || cal2.is_empty()) {
1254 cal1.moveto(point1[0]);
1255 cal2.moveto(point2[0]);
1257#ifdef ERASER_VERBOSE
1258 g_print(
"[F&S:#] npoints:%d, releasing:%s\n", npoints, releasing ?
"TRUE" :
"FALSE");
1261 unsigned const bezier_size = 4;
1262 unsigned const max_beziers = 8;
1263 size_t const bezier_max_length = bezier_size * max_beziers;
1267 g_assert(nb1 * bezier_size <= gint(G_N_ELEMENTS(b1)));
1271 g_assert(nb2 * bezier_size <= gint(G_N_ELEMENTS(b2)));
1273 if (nb1 == -1 || nb2 == -1) {
1274 _failedBezierFallback();
1279#ifdef ERASER_VERBOSE
1280 g_print(
"nb1:%d nb2:%d\n", nb1, nb2);
1285 currentcurve.reset();
1286 currentcurve.moveto(b1[0]);
1288 for (
Geom::Point *bp1 = b1; bp1 < b1 + bezier_size * nb1; bp1 += bezier_size) {
1289 currentcurve.curveto(bp1[1], bp1[2], bp1[3]);
1292 currentcurve.lineto(b2[bezier_size * (nb2 - 1) + 3]);
1294 for (
Geom::Point *bp2 = b2 + bezier_size * (nb2 - 1); bp2 >= b2; bp2 -= bezier_size) {
1295 currentcurve.curveto(bp2[2], bp2[1], bp2[0]);
1299 if (segments.empty()) {
1300 _addCap(currentcurve, b2[1], b2[0], b1[0], b1[1], cap_rounding);
1303 currentcurve.closepath();
1304 currentshape->set_bpath(¤tcurve,
true);
1308 for (
Geom::Point *bp1 = b1; bp1 < b1 + bezier_size * nb1; bp1 += bezier_size) {
1309 cal1.curveto(bp1[1], bp1[2], bp1[3]);
1312 for (
Geom::Point *bp2 = b2; bp2 < b2 + bezier_size * nb2; bp2 += bezier_size) {
1313 cal2.curveto(bp2[1], bp2[2], bp2[3]);
1317void EraserTool::_failedBezierFallback()
1320#ifdef ERASER_VERBOSE
1321 g_print(
"[_failedBezierFallback] - failed to fit cubic.\n");
1323 _drawTemporaryBox();
1325 for (gint i = 1; i < npoints; i++) {
1326 cal1.lineto(point1[i]);
1329 for (gint i = 1; i < npoints; i++) {
1330 cal2.lineto(point2[i]);
1334void EraserTool::_fitDrawLastPoint()
1336 g_assert(!currentcurve.is_empty());
1344 cbp->set_fill(fillColor ? fillColor->toRGBA(opacity * fillOpacity) : 0x0, trace_wind_rule);
1345 cbp->set_stroke(0x0);
1349 segments.emplace_back(cbp);
1351 if (
mode == EraserToolMode::DELETE) {
1352 cbp->set_visible(
false);
1353 currentshape->set_visible(
false);
1357void EraserTool::_drawTemporaryBox()
1359 currentcurve.reset();
1361 currentcurve.moveto(point1[npoints - 1]);
1363 for (gint i = npoints - 2; i >= 0; i--) {
1364 currentcurve.lineto(point1[i]);
1367 for (gint i = 0; i < npoints; i++) {
1368 currentcurve.lineto(point2[i]);
1372 _addCap(currentcurve,
1373 point2[npoints - 2], point2[npoints - 1],
1374 point1[npoints - 1], point1[npoints - 2], cap_rounding);
1377 currentcurve.closepath();
1378 currentshape->set_bpath(¤tcurve,
true);
Bezier fitting algorithms.
3x3 matrix representing an affine transformation.
Affine inverse() const
Compute the inverse matrix.
Two-dimensional Bezier curve of arbitrary order.
Point finalPoint() const override
Retrieve the end of the curve.
Point initialPoint() const override
Retrieve the start of the curve.
virtual Point unitTangentAt(Coord t, unsigned n=3) const
Compute a vector tangent to the curve.
Axis-aligned rectangle that can be empty.
Two-dimensional point that doubles as a vector.
static void done(SPDocument *document, Glib::ustring const &event_description, Glib::ustring const &undo_icon, unsigned int object_modified_tag=0)
static void cancel(SPDocument *document)
bool remove(SPObject *object)
Removes an item from the set of selected objects.
void breakApart(bool skip_undo=false, bool overlapping=true, bool silent=false)
SPItemRange items()
Returns a range of selected SPItems.
void pathUnion(bool skip_undo=false, bool silent=false)
void removeLPESRecursive(bool keep_paths)
void pathCut(bool skip_undo=false, bool silent=false)
bool add(SPObject *object, bool nosignal=false)
Add an SPObject to the set of selected objects.
void raiseToTop(bool skip_undo=false)
void pathDiff(bool skip_undo=false, bool silent=false)
void clear()
Unselects all selected objects.
bool isEmpty()
Returns true if no items are selected.
void set(SPObject *object, bool persist_selection_context=false)
Set the selection to a single specific object.
void combine(bool skip_undo=false, bool silent=false)
void setMask(bool apply_clip_path, bool apply_to_layer, bool remove_original)
Creates a mask or clipPath from selection.
std::function< void()> action
The action to perform when the value changes, if any.
static Rubberband * get(SPDesktop *desktop)
void move(Geom::Point const &p)
The set of selected SPObjects for a given document and layer model.
void add(XML::Node *repr)
Add an XML node's SPObject to the set of selected objects.
Interface for refcounted XML nodes.
virtual Node * duplicate(Document *doc) const =0
Create a duplicate of this node.
virtual char const * attribute(char const *key) const =0
Get the string representation of a node's attribute.
Wrapper around a Geom::PathVector object.
void reset()
Set curve to empty curve.
To do: update description of desktop.
double current_zoom() const
Inkscape::CanvasItemGroup * getCanvasSketch() const
Geom::Affine const & w2d() const
Transformation from window to desktop coordinates (zoom/rotate).
Typed SVG document implementation.
Inkscape::XML::Document * getReprDoc()
Our Inkscape::XML::Document.
SPObject * getObjectByRepr(Inkscape::XML::Node *repr) const
Base class for visual SVG elements.
virtual std::optional< Geom::PathVector > documentExactBounds() const
Get an exact geometric shape representing the visual bounds of the item in the document coordinates.
Geom::OptRect documentVisualBounds() const
Get item's visual bbox in document coordinate system.
Geom::Affine getRelativeTransform(SPObject const *obj) const
SPClipPath * getClipObject() const
std::vector< SPObject * > childList(bool add_ref, Action action=ActionGeneral)
Retrieves the children as a std vector object, optionally ref'ing the children in the process,...
SPStyle * style
Represents the style properties, whether from presentation attributes, the style attribute,...
void deleteObject(bool propagate, bool propagate_descendants)
Deletes an object, unparenting it from its parent.
SPObject * appendChildRepr(Inkscape::XML::Node *repr)
Append repr as child of this object.
std::shared_ptr< Css const > css
bool sp_desktop_root_handler(Inkscape::CanvasEvent const &event, SPDesktop *desktop)
void sp_desktop_apply_style_tool(SPDesktop *desktop, Inkscape::XML::Node *repr, Glib::ustring const &tool_path, bool with_text)
Apply the desktop's current style or the tool style to repr.
double sp_desktop_get_master_opacity_tool(SPDesktop *desktop, Glib::ustring const &tool, bool *has_opacity)
std::optional< Color > sp_desktop_get_color_tool(SPDesktop *desktop, Glib::ustring const &tool, bool is_fill)
double sp_desktop_get_opacity_tool(SPDesktop *desktop, Glib::ustring const &tool, bool is_fill)
void sp_desktop_set_style(SPDesktop *desktop, SPCSSAttr *css, bool change, bool write_current, bool switch_style)
Apply style on selection on desktop.
Editable view implementation.
TODO: insert short description here.
constexpr int SAMPLING_SIZE
Macro for icon names used in Inkscape.
Interface for locally managing a current status message.
Raw stack of active status messages.
double atan2(Point const &p)
bool is_zero(Point const &p)
int bezier_fit_cubic_r(Point bezier[], Point const data[], int len, double error, unsigned max_beziers)
Fit a multi-segment Bezier curve to a set of digitized points, with possible weedout of identical poi...
SBasis L2(D2< SBasis > const &a, unsigned k)
bool are_near(Affine const &a1, Affine const &a2, Coord eps=EPSILON)
D2< T > rot90(D2< T > const &a)
Point middle_point(LineSegment const &_segment)
static R & release(R &r)
Decrements the reference count of a anchored object.
void inspect_event(E &&event, Fs... funcs)
Perform pattern-matching on a CanvasEvent.
std::uint_least32_t MessageId
An integer ID which identifies a displayed message in a particular Inkscape::MessageStack.
@ RUBBERBAND_TOUCHPATH_ERASER
static cairo_user_data_key_t key
bool sp_item_list_to_curves(const std::vector< SPItem * > &items, std::vector< SPItem * > &selected, std::vector< Inkscape::XML::Node * > &to_select, bool skip_all_lpeitems)
PathVector - a sequence of subpaths.
Singleton class to access the preferences file in a convenient way.
Piecewise< SBasis > log(Interval in)
SPCSSAttr * sp_repr_css_attr_new()
Creates an empty SPCSSAttr (a class for manipulating CSS style properties).
void sp_repr_css_attr_unref(SPCSSAttr *css)
Unreferences an SPCSSAttr (will be garbage collected if no references remain).
void sp_repr_css_set_property(SPCSSAttr *css, gchar const *name, gchar const *value)
Set a style property to a new value (e.g.
void sp_repr_unparent(Inkscape::XML::Node *repr)
Remove repr from children of its parent node.
SVG <image> implementation.
Abstract base class for events.
Movement of the mouse pointer.
Interface for XML documents.
virtual Node * createElement(char const *name)=0
SPStyle - a style object for SPItem objects.
static void sp_svg_write_path(Inkscape::SVG::PathString &str, Geom::Path const &p, bool normalize=false)
static double square(double const x)