Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
persp3d.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3 * Class modelling a 3D perspective as an SPObject
4 *
5 * Authors:
6 * Maximilian Albert <Anhalter42@gmx.de>
7 * Jon A. Cruz <jon@joncruz.org>
8 * Abhishek Sharma
9 *
10 * Copyright (C) 2007 authors
11 *
12 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
13 */
14
15#include "persp3d.h"
16
17#include <glibmm/i18n.h>
18
19
20#include "attributes.h"
21#include "box3d.h"
22#include "desktop.h"
23#include "document-undo.h"
24#include "perspective-line.h"
25#include "sp-defs.h"
26#include "sp-root.h"
27#include "vanishing-point.h"
28
29#include "svg/stringstream.h"
30#include "ui/icon-names.h"
31#include "ui/tools/box3d-tool.h"
32#include "util/units.h"
33#include "xml/node.h"
34#include "xml/node-observer.h"
35
41
43
44static int global_counter = 0;
45
46/* Constructor/destructor for the internal class */
47
52
54 : perspective_impl(std::make_unique<Persp3DImpl>())
55{}
56
57Persp3D::~Persp3D() = default;
58
59
75
86
91 // Read values are in 'user units'.
92 auto root = doc->getRoot();
93 if (root->viewBox_set) {
94 pt[0] *= root->width.computed / root->viewBox.width();
95 pt[1] *= root->height.computed / root->viewBox.height();
96 }
97
98 // <inkscape:perspective> stores inverted y-axis coordinates
99 if (doc->is_yaxisdown()) {
100 pt[1] *= -1;
101 if (pt[2]) {
102 pt[1] += doc->getHeight().value("px");
103 }
104 }
105
106 return pt;
107}
108
113 // <inkscape:perspective> stores inverted y-axis coordinates
114 if (doc->is_yaxisdown()) {
115 pt[1] *= -1;
116 if (pt[2]) {
117 pt[1] += doc->getHeight().value("px");
118 }
119 }
120
121 // Written values are in 'user units'.
122 auto root = doc->getRoot();
123 if (root->viewBox_set) {
124 pt[0] *= root->viewBox.width() / root->width.computed;
125 pt[1] *= root->viewBox.height() / root->height.computed;
126 }
127
128 return pt;
129}
130
134// FIXME: Currently we only read the finite positions of vanishing points;
135// should we move VPs into their own repr (as it's done for SPStop, e.g.)?
136void Persp3D::set(SPAttr key, gchar const *value) {
137
138 switch (key) {
140 if (value) {
141 Proj::Pt2 pt (value);
143 perspective_impl->tmat.set_image_pt( Proj::X, ptn );
144 }
145 break;
146 }
148 if (value) {
149 Proj::Pt2 pt (value);
151 perspective_impl->tmat.set_image_pt( Proj::Y, ptn );
152 }
153 break;
154 }
156 if (value) {
157 Proj::Pt2 pt (value);
159 perspective_impl->tmat.set_image_pt( Proj::Z, ptn );
160 }
161 break;
162 }
164 if (value) {
165 Proj::Pt2 pt (value);
167 perspective_impl->tmat.set_image_pt( Proj::W, ptn );
168 }
169 break;
170 }
171 default: {
172 SPObject::set(key, value);
173 break;
174 }
175 }
176
177 // FIXME: Is this the right place for resetting the draggers? PROBABLY NOT!
178 if (!SP_ACTIVE_DESKTOP) {
179 // Maybe in commandline mode.
180 return;
181 }
182
184 if (auto bc = dynamic_cast<Inkscape::UI::Tools::Box3dTool*>(tool)) {
185 bc->_vpdrag->updateDraggers();
186 bc->_vpdrag->updateLines();
187 bc->_vpdrag->updateBoxHandles();
188 bc->_vpdrag->updateBoxReprs();
189 }
190}
191
192void Persp3D::update(SPCtx *ctx, guint flags) {
193 if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG)) {
194
195 /* TODO: Should we update anything here? */
196
197 }
198
199 SPObject::update(ctx, flags);
200}
201
202Persp3D *
204 SPDefs *defs = document->getDefs();
207
208 /* if no perspective is given, create a default one */
209 repr = xml_doc->createElement("inkscape:perspective");
210 repr->setAttribute("sodipodi:type", "inkscape:persp3d");
211
212 // Use 'user-units'
213 double width = document->getWidth().value("px");
214 double height = document->getHeight().value("px");
215 if( document->getRoot()->viewBox_set ) {
217 width = vb.width();
218 height = vb.height();
219 }
220
221 Proj::Pt2 proj_vp_x = Proj::Pt2 (0.0, height/2.0, 1.0);
222 Proj::Pt2 proj_vp_y = Proj::Pt2 (0.0, 1000.0, 0.0);
223 Proj::Pt2 proj_vp_z = Proj::Pt2 (width, height/2.0, 1.0);
224 Proj::Pt2 proj_origin = Proj::Pt2 (width/2.0, height/3.0, 1.0 );
225
226 gchar *str = nullptr;
227 str = proj_vp_x.coord_string();
228 repr->setAttribute("inkscape:vp_x", str);
229 g_free (str);
230 str = proj_vp_y.coord_string();
231 repr->setAttribute("inkscape:vp_y", str);
232 g_free (str);
233 str = proj_vp_z.coord_string();
234 repr->setAttribute("inkscape:vp_z", str);
235 g_free (str);
236 str = proj_origin.coord_string();
237 repr->setAttribute("inkscape:persp3d-origin", str);
238 g_free (str);
239
240 /* Append the new persp3d to defs */
241 defs->getRepr()->addChild(repr, nullptr);
243
244 return reinterpret_cast<Persp3D *>( defs->get_child_by_repr(repr) );
245}
246
247Persp3D *
249{
250 Persp3D *first = nullptr;
251 for (auto& child: document->getDefs()->children) {
252 if (is<Persp3D>(&child)) {
253 first = cast<Persp3D>(&child);
254 break;
255 }
256 }
257 return first;
258}
259
264
265 if ((flags & SP_OBJECT_WRITE_BUILD & SP_OBJECT_WRITE_EXT) && !repr) {
266 // this is where we end up when saving as plain SVG (also in other circumstances?);
267 // hence we don't set the sodipodi:type attribute
268 repr = xml_doc->createElement("inkscape:perspective");
269 }
270
271 if (flags & SP_OBJECT_WRITE_EXT) {
272 {
273 Proj::Pt2 pt = perspective_impl->tmat.column( Proj::X );
276 os << pt[0] << " : " << pt[1] << " : " << pt[2];
277 repr->setAttribute("inkscape:vp_x", os.str());
278 }
279 {
280 Proj::Pt2 pt = perspective_impl->tmat.column( Proj::Y );
283 os << pt[0] << " : " << pt[1] << " : " << pt[2];
284 repr->setAttribute("inkscape:vp_y", os.str());
285 }
286 {
287 Proj::Pt2 pt = perspective_impl->tmat.column( Proj::Z );
290 os << pt[0] << " : " << pt[1] << " : " << pt[2];
291 repr->setAttribute("inkscape:vp_z", os.str());
292 }
293 {
294 Proj::Pt2 pt = perspective_impl->tmat.column( Proj::W );
297 os << pt[0] << " : " << pt[1] << " : " << pt[2];
298 repr->setAttribute("inkscape:persp3d-origin", os.str());
299 }
300 }
301
302 SPObject::write(xml_doc, repr, flags);
303
304 return repr;
305}
306
307/* convenience wrapper around Persp3D::get_finite_dir() and Persp3D::get_infinite_dir() */
310 if (Persp3D::VP_is_finite(this->perspective_impl.get(), axis)) {
311 return this->get_finite_dir(pt, axis);
312 } else {
313 return this->get_infinite_dir(axis);
314 }
315}
316
319 Box3D::PerspectiveLine pl(pt, axis, this);
320 return pl.direction();
321}
322
325 Proj::Pt2 vp(this->get_VP(axis));
326 if (vp[2] != 0.0) {
327 g_warning ("VP should be infinite but is (%f : %f : %f)", vp[0], vp[1], vp[2]);
328 g_return_val_if_fail(vp[2] != 0.0, Geom::Point(0.0, 0.0));
329 }
330 return Geom::Point(vp[0], vp[1]);
331}
332
333double
335 return this->perspective_impl->tmat.get_infinite_angle(axis);
336}
337
338bool
340 return persp_impl->tmat.has_finite_image(axis);
341}
342
343void
344Persp3D::toggle_VP (Proj::Axis axis, bool set_undo) {
345 this->perspective_impl->tmat.toggle_finite(axis);
346 // FIXME: Remove this repr update and rely on vp_drag_sel_modified() to do this for us
347 // On the other hand, vp_drag_sel_modified() would update all boxes;
348 // here we can confine ourselves to the boxes of this particular perspective.
349 this->update_box_reprs();
350 this->updateRepr(SP_OBJECT_WRITE_EXT);
351 if (set_undo) {
352 DocumentUndo::done(SP_ACTIVE_DESKTOP->getDocument(), _("Toggle vanishing point"), INKSCAPE_ICON("draw-cuboid"));
353 }
354}
355
356/* toggle VPs for the same axis in all perspectives of a given list */
357void
358Persp3D::toggle_VPs (std::list<Persp3D *> list, Proj::Axis axis) {
359 for (Persp3D *persp : list) {
360 persp->toggle_VP(axis, false);
361 }
362 DocumentUndo::done(SP_ACTIVE_DESKTOP->getDocument(), _("Toggle multiple vanishing points"), INKSCAPE_ICON("draw-cuboid"));
363}
364
365void
367 if (Persp3D::VP_is_finite(this->perspective_impl.get(), axis) != (state == Proj::VP_FINITE)) {
368 this->toggle_VP(axis);
369 }
370}
371
372void
373Persp3D::rotate_VP (Proj::Axis axis, double angle, bool alt_pressed) { // angle is in degrees
374 // FIXME: Most of this functionality should be moved to trans_mat_3x4.(h|cpp)
375 if (this->perspective_impl->tmat.has_finite_image(axis)) {
376 // don't rotate anything for finite VPs
377 return;
378 }
379 Proj::Pt2 v_dir_proj (this->perspective_impl->tmat.column(axis));
380 Geom::Point v_dir (v_dir_proj[0], v_dir_proj[1]);
381 double a = Geom::atan2 (v_dir) * 180/M_PI;
382 a += alt_pressed ? 0.5 * ((angle > 0 ) - (angle < 0)) : angle; // the r.h.s. yields +/-0.5 or angle
383 this->perspective_impl->tmat.set_infinite_direction (axis, a);
384
385 this->update_box_reprs ();
386 this->updateRepr(SP_OBJECT_WRITE_EXT);
387}
388
389void
391 this->perspective_impl->tmat *= xform;
392 this->update_box_reprs();
393 this->updateRepr(SP_OBJECT_WRITE_EXT);
394}
395
396void
398 auto persp_impl = perspective_impl.get();
399
400 if (!box) {
401 return;
402 }
403 if (std::find (persp_impl->boxes.begin(), persp_impl->boxes.end(), box) != persp_impl->boxes.end()) {
404 return;
405 }
406 persp_impl->boxes.push_back(box);
407}
408
409void
411 auto persp_impl = perspective_impl.get();
412
413 std::vector<SPBox3D *>::iterator i = std::find (persp_impl->boxes.begin(), persp_impl->boxes.end(), box);
414 if (i != persp_impl->boxes.end())
415 persp_impl->boxes.erase(i);
416}
417
418bool
420 auto persp_impl = perspective_impl.get();
421
422 // FIXME: For some reason, std::find() does not seem to compare pointers "correctly" (or do we need to
423 // provide a proper comparison function?), so we manually traverse the list.
424 for (auto & boxe : persp_impl->boxes) {
425 if (boxe == box) {
426 return true;
427 }
428 }
429 return false;
430}
431
432void
434 auto persp_impl = perspective_impl.get();
435
436 if (persp_impl->boxes.empty())
437 return;
438 for (auto & boxe : persp_impl->boxes) {
439 boxe->position_set();
440 }
441}
442
443void
445 auto persp_impl = perspective_impl.get();
446
447 if (!persp_impl || persp_impl->boxes.empty())
448 return;
449 for (auto & boxe : persp_impl->boxes) {
450 boxe->updateRepr(SP_OBJECT_WRITE_EXT);
451 boxe->set_z_orders();
452 }
453}
454
455void
457 auto persp_impl = perspective_impl.get();
458
459 if (!persp_impl || persp_impl->boxes.empty())
460 return;
461 for (auto & boxe : persp_impl->boxes) {
462 boxe->set_z_orders();
463 }
464}
465
466// FIXME: For some reason we seem to require a vector instead of a list in Persp3D, but in vp_knot_moved_handler()
467// we need a list of boxes. If we can store a list in Persp3D right from the start, this function becomes
468// obsolete. We should do this.
469std::list<SPBox3D *>
471 auto persp_impl = perspective_impl.get();
472
473 std::list<SPBox3D *> bx_lst;
474 for (auto & boxe : persp_impl->boxes) {
475 bx_lst.push_back(boxe);
476 }
477 return bx_lst;
478}
479
480bool
482{
483 return this->perspective_impl->tmat == other->perspective_impl->tmat;
484}
485
486void
488 /* double check if we are called in sane situations */
489 g_return_if_fail (this->perspectives_coincide(other) && this != other);
490
491 // Note: We first need to copy the boxes of other into a separate list;
492 // otherwise the loop below gets confused when perspectives are reattached.
493 std::list<SPBox3D *> boxes_of_persp2 = other->list_of_boxes();
494
495 for (auto & box : boxes_of_persp2) {
496 box->switch_perspectives(other, this, true);
497 box->updateRepr(SP_OBJECT_WRITE_EXT); // so that undo/redo can do its job properly
498 }
499}
500
501/* checks whether all boxes linked to this perspective are currently selected */
502bool
504 auto persp_impl = perspective_impl.get();
505
506 std::list<SPBox3D *> selboxes = set->box3DList();
507
508 for (auto & boxe : persp_impl->boxes) {
509 if (std::find(selboxes.begin(), selboxes.end(), boxe) == selboxes.end()) {
510 // we have an unselected box in the perspective
511 return false;
512 }
513 }
514 return true;
515}
516
517/* some debugging stuff follows */
518
519void
521 auto persp_impl = perspective_impl.get();
522 g_print ("=== Info for Persp3D %d ===\n", persp_impl->my_counter);
523 gchar * cstr;
524 for (auto & axe : Proj::axes) {
525 cstr = this->get_VP(axe).coord_string();
526 g_print (" VP %s: %s\n", Proj::string_from_axis(axe), cstr);
527 g_free(cstr);
528 }
529 cstr = this->get_VP(Proj::W).coord_string();
530 g_print (" Origin: %s\n", cstr);
531 g_free(cstr);
532
533 g_print (" Boxes: ");
534 for (auto & boxe : persp_impl->boxes) {
535 g_print ("%d (%d) ", boxe->my_counter, boxe->get_perspective()->perspective_impl->my_counter);
536 }
537 g_print ("\n");
538 g_print ("========================\n");
539}
540
541void
543{
544 for (auto& child: document->getDefs()->children) {
545 if (is<Persp3D>(&child)) {
546 cast<Persp3D>(&child)->print_debugging_info();
547 }
548 }
550}
551
552void
554 g_print ("\n======================================\n");
555 g_print ("Selected perspectives and their boxes:\n");
556
557 std::list<Persp3D *> sel_persps = SP_ACTIVE_DESKTOP->getSelection()->perspList();
558
559 for (auto & sel_persp : sel_persps) {
560 auto persp = sel_persp;
561 auto persp_impl = persp->perspective_impl.get();
562 g_print (" %s (%d): ", persp->getRepr()->attribute("id"), persp->perspective_impl->my_counter);
563 for (auto & boxe : persp_impl->boxes) {
564 g_print ("%d ", boxe->my_counter);
565 }
566 g_print ("\n");
567 }
568 g_print ("======================================\n\n");
569 }
570
571void print_current_persp3d(gchar *func_name, Persp3D *persp) {
572 g_print ("%s: current_persp3d is now %s\n",
573 func_name,
574 persp ? persp->getRepr()->attribute("id") : "NULL");
575}
576
577/*
578 Local Variables:
579 mode:c++
580 c-file-style:"stroustrup"
581 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
582 indent-tabs-mode:nil
583 fill-column:99
584 End:
585*/
586// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
Lookup dictionary for attributes/properties.
SPAttr
Definition attributes.h:27
@ INKSCAPE_PERSP3D_ORIGIN
@ INKSCAPE_PERSP3D_VP_X
@ INKSCAPE_PERSP3D_VP_Z
@ INKSCAPE_PERSP3D_VP_Y
Geom::Point direction()
3x3 matrix representing an affine transformation.
Definition affine.h:70
C height() const
Get the vertical extent of the rectangle.
C width() const
Get the horizontal extent of the rectangle.
Two-dimensional point that doubles as a vector.
Definition point.h:66
Axis aligned, non-empty rectangle.
Definition rect.h:92
static Application & instance()
Returns the current Inkscape::Application global object.
Definition inkscape.cpp:152
SPDesktop * active_desktop()
Definition inkscape.cpp:759
std::string str() const
double value(Unit const *u) const
Return the quantity's value in the specified unit.
Definition units.cpp:565
Interface for refcounted XML nodes.
Definition node.h:80
virtual void addChild(Node *child, Node *after)=0
Insert another node as a child of this node.
void setAttribute(Util::const_char_ptr key, Util::const_char_ptr value)
Change an attribute of this node.
Definition node.cpp:25
virtual char const * attribute(char const *key) const =0
Get the string representation of a node's attribute.
virtual void addObserver(NodeObserver &observer)=0
Add an object that will be notified of the changes to this node.
virtual void removeObserver(NodeObserver &observer)=0
Remove an object from the list of observers.
Proj::TransfMat3x4 tmat
Definition persp3d.h:46
int my_counter
Definition persp3d.h:53
void notifyAttributeChanged(Inkscape::XML::Node &, GQuark, Inkscape::Util::ptr_shared, Inkscape::Util::ptr_shared) final
Attribute change callback.
Definition persp3d.cpp:36
void build(SPDocument *doc, Inkscape::XML::Node *repr) override
Virtual build: set persp3d attributes from its associated XML node.
Definition persp3d.cpp:63
std::list< SPBox3D * > list_of_boxes() const
Definition persp3d.cpp:470
static void toggle_VPs(std::list< Persp3D * >, Proj::Axis axis)
Definition persp3d.cpp:358
double get_infinite_angle(Proj::Axis axis) const
Definition persp3d.cpp:334
void toggle_VP(Proj::Axis axis, bool set_undo=true)
Definition persp3d.cpp:344
bool perspectives_coincide(Persp3D const *rhs) const
Definition persp3d.cpp:481
Persp3DNodeObserver & nodeObserver()
Definition persp3d.h:121
Persp3D()
Definition persp3d.cpp:53
void set(SPAttr key, char const *value) override
Virtual set: set attribute to value.
Definition persp3d.cpp:136
~Persp3D() override
void add_box(SPBox3D *box)
Definition persp3d.cpp:397
static Persp3D * document_first_persp(SPDocument *document)
Definition persp3d.cpp:248
void update_box_displays()
Definition persp3d.cpp:433
Geom::Point get_infinite_dir(Proj::Axis axis) const
Definition persp3d.cpp:324
static Persp3D * create_xml_element(SPDocument *document)
Definition persp3d.cpp:203
void set_VP_state(Proj::Axis axis, Proj::VPState state)
Definition persp3d.cpp:366
void rotate_VP(Proj::Axis axis, double angle, bool alt_pressed)
Definition persp3d.cpp:373
void update(SPCtx *ctx, unsigned int flags) override
Definition persp3d.cpp:192
void update_box_reprs()
Definition persp3d.cpp:444
bool has_box(SPBox3D *box) const
Definition persp3d.cpp:419
Geom::Point get_PL_dir_from_pt(Geom::Point const &pt, Proj::Axis axis) const
Definition persp3d.cpp:309
void apply_affine_transformation(Geom::Affine const &xform)
Definition persp3d.cpp:390
std::unique_ptr< Persp3DImpl > perspective_impl
Definition persp3d.h:65
static void print_debugging_info_all(SPDocument *doc)
Definition persp3d.cpp:542
void print_debugging_info() const
Definition persp3d.cpp:520
bool has_all_boxes_in_selection(Inkscape::ObjectSet *set) const
Definition persp3d.cpp:503
Proj::Pt2 get_VP(Proj::Axis axis) const
Definition persp3d.h:83
void release() override
Virtual release of Persp3D members before destruction.
Definition persp3d.cpp:79
Geom::Point get_finite_dir(Geom::Point const &pt, Proj::Axis axis) const
Definition persp3d.cpp:318
static bool VP_is_finite(Persp3DImpl *persp_impl, Proj::Axis axis)
Definition persp3d.cpp:339
Inkscape::XML::Node * write(Inkscape::XML::Document *doc, Inkscape::XML::Node *repr, unsigned int flags) override
Virtual write: write object attributes to repr.
Definition persp3d.cpp:263
static void print_all_selected()
Definition persp3d.cpp:553
void absorb(Persp3D *persp2)
Definition persp3d.cpp:487
void remove_box(SPBox3D *box)
Definition persp3d.cpp:410
void update_z_orders()
Definition persp3d.cpp:456
char * coord_string()
Definition proj_pt.cpp:59
bool has_finite_image(Proj::Axis axis)
Inkscape::UI::Tools::ToolBase * getTool() const
Definition desktop.h:187
Typed SVG document implementation.
Definition document.h:101
SPRoot * getRoot()
Returns our SPRoot.
Definition document.h:200
Inkscape::Util::Quantity getWidth() const
Definition document.cpp:848
SPDefs * getDefs()
Return the main defs object for the document.
Definition document.cpp:247
Inkscape::XML::Document * getReprDoc()
Our Inkscape::XML::Document.
Definition document.h:211
bool is_yaxisdown() const
True if the desktop Y-axis points down, false if it points up.
Definition document.h:274
Inkscape::Util::Quantity getHeight() const
Definition document.cpp:887
Inkscape::XML::Node * repr
Definition sp-object.h:193
SPObject * get_child_by_repr(Inkscape::XML::Node *repr)
Return object's child whose node pointer equals repr.
SPDocument * document
Definition sp-object.h:188
virtual void set(SPAttr key, const char *value)
virtual Inkscape::XML::Node * write(Inkscape::XML::Document *doc, Inkscape::XML::Node *repr, unsigned int flags)
virtual void update(SPCtx *ctx, unsigned int flags)
virtual void release()
Inkscape::XML::Node * updateRepr(unsigned int flags=SP_OBJECT_WRITE_EXT)
Updates the object's repr based on the object's state.
void readAttr(char const *key)
Read value of key attribute from XML node into object.
Inkscape::XML::Node * getRepr()
Returns the XML representation of tree.
virtual void build(SPDocument *doc, Inkscape::XML::Node *repr)
ChildrenList children
Definition sp-object.h:907
Geom::Rect viewBox
Definition viewbox.h:35
bool viewBox_set
Definition viewbox.h:34
RootCluster root
Editable view implementation.
TODO: insert short description here.
Macro for icon names used in Inkscape.
double atan2(Point const &p)
static R & release(R &r)
Decrements the reference count of a anchored object.
@ VP_FINITE
Definition axis-manip.h:24
char const * string_from_axis(Proj::Axis axis)
Definition axis-manip.h:41
Axis axes[4]
STL namespace.
Interface for XML node observers.
static cairo_user_data_key_t key
static int global_counter
Definition persp3d.cpp:44
void print_current_persp3d(gchar *func_name, Persp3D *persp)
Definition persp3d.cpp:571
static Proj::Pt2 legacy_transform_forward(Proj::Pt2 pt, SPDocument const *doc)
Apply viewBox and legacy desktop transformation to point loaded from SVG.
Definition persp3d.cpp:90
static Proj::Pt2 legacy_transform_backward(Proj::Pt2 pt, SPDocument const *doc)
Apply viewBox and legacy desktop transformation to point to be written to SVG.
Definition persp3d.cpp:112
Ocnode * child[8]
Definition quantize.cpp:33
guint32 GQuark
SPRoot: SVG <svg> implementation.
Interface for XML documents.
Definition document.h:43
virtual Node * createElement(char const *name)=0
Unused.
Definition sp-object.h:94
TODO: insert short description here.
double height
double width
Interface for XML nodes.